Skip to content

Commit b11fb12

Browse files
committed
some changes
1 parent 9dda6fd commit b11fb12

7 files changed

Lines changed: 181 additions & 33 deletions

File tree

src/chrome/src/agent/agent.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2879,6 +2879,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28792879
? challenge.frameId
28802880
: null,
28812881
captchaChallengeFrameUrl: challenge.frameUrl || '',
2882+
captchaChallengeVisibilityConfirmed: true,
28822883
},
28832884
{},
28842885
);
@@ -2897,6 +2898,11 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
28972898
}
28982899

28992900
const activeGate = this._captchaGateStates.get(tabId);
2901+
const treeFilter = String(toolArgs?.filter || 'all').toLowerCase();
2902+
let observedChallengeFrameId = Number.isInteger(toolResult.captchaChallengeFrameId)
2903+
? toolResult.captchaChallengeFrameId
2904+
: null;
2905+
let observedChallengeFrameUrl = String(toolResult.captchaChallengeFrameUrl || '');
29002906
let challenge = detectChallengeDialog(toolResult.pageContent, {
29012907
allowGenericFailure: !!activeGate,
29022908
});
@@ -2906,11 +2912,31 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
29062912
{ allowGenericFailure: !!activeGate },
29072913
);
29082914
}
2915+
if (
2916+
challenge
2917+
&& treeFilter !== 'visible'
2918+
&& toolResult.captchaChallengeVisibilityConfirmed !== true
2919+
) {
2920+
const visibleChallenge = await this._detectChallengeDialogBeforeMutation(tabId, {
2921+
allowGenericFailure: !!activeGate,
2922+
});
2923+
if (visibleChallenge?.label) {
2924+
challenge = detectChallengeDialog(
2925+
`dialog ${JSON.stringify(String(visibleChallenge.label).slice(0, 200))}`,
2926+
{ allowGenericFailure: !!activeGate },
2927+
);
2928+
observedChallengeFrameId = Number.isInteger(visibleChallenge.frameId)
2929+
? visibleChallenge.frameId
2930+
: null;
2931+
observedChallengeFrameUrl = String(visibleChallenge.frameUrl || '');
2932+
} else {
2933+
challenge = null;
2934+
}
2935+
}
29092936
let pageUrl = String(toolResult.currentUrl || toolResult.pageUrl || '');
29102937
if (!pageUrl) {
29112938
try { pageUrl = await this._currentUrl(tabId); } catch {}
29122939
}
2913-
const treeFilter = String(toolArgs?.filter || 'all').toLowerCase();
29142940
const requestedPage = toolArgs?.page;
29152941
const requestedMaxDepth = toolArgs?.maxDepth;
29162942
const parsedMaxDepth = Number(requestedMaxDepth);
@@ -3108,10 +3134,10 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
31083134
key,
31093135
status: publicGate.status,
31103136
publicGate,
3111-
...(Number.isInteger(toolResult.captchaChallengeFrameId)
3137+
...(Number.isInteger(observedChallengeFrameId)
31123138
? {
3113-
challengeFrameId: toolResult.captchaChallengeFrameId,
3114-
challengeFrameUrl: String(toolResult.captchaChallengeFrameUrl || ''),
3139+
challengeFrameId: observedChallengeFrameId,
3140+
challengeFrameUrl: observedChallengeFrameUrl,
31153141
}
31163142
: {}),
31173143
});

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -627,11 +627,15 @@ export function detectCaptchaCandidatesInPage(scope = null) {
627627
const visibleElement = (element) => {
628628
if (!element) return false;
629629
try {
630-
const style = typeof pageWindow?.getComputedStyle === 'function'
631-
? pageWindow.getComputedStyle(element)
632-
: null;
633-
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) return false;
634-
if (element.hidden || element.getAttribute?.('aria-hidden') === 'true') return false;
630+
let current = element;
631+
for (let depth = 0; current && depth < 30; depth += 1) {
632+
const style = typeof pageWindow?.getComputedStyle === 'function'
633+
? pageWindow.getComputedStyle(current)
634+
: null;
635+
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) return false;
636+
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
637+
current = current.parentElement || null;
638+
}
635639
if (typeof element.getBoundingClientRect === 'function') {
636640
const rect = element.getBoundingClientRect();
637641
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
@@ -657,13 +661,21 @@ export function detectCaptchaCandidatesInPage(scope = null) {
657661
.map(id => pageDocument.getElementById?.(id)?.textContent || '')
658662
.join(' ');
659663
} catch (_) {}
664+
let renderedHeadings = '';
665+
try {
666+
renderedHeadings = Array.from(
667+
element.querySelectorAll?.('h1, h2, h3, [role="heading"]') || []
668+
)
669+
.filter(visibleElement)
670+
.map(heading => heading.textContent || '')
671+
.join(' ');
672+
} catch (_) {}
660673
return [
661674
element.getAttribute?.('aria-label'),
662675
labelledBy,
663-
element.querySelector?.('h1, h2, h3, [role="heading"]')?.textContent,
676+
renderedHeadings,
664677
element.getAttribute?.('title'),
665678
element.innerText,
666-
element.textContent,
667679
].some(value => challengeDialogRe.test(String(value || '')));
668680
});
669681
const elementInChallengeDialog = (element) => {

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,13 @@ export function detectChallengeDialogInPage(options = null) {
107107
};
108108
const visible = (element) => {
109109
try {
110-
const style = getComputedStyle(element);
111-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
112-
if (element.hidden || element.getAttribute?.('aria-hidden') === 'true') return false;
110+
let current = element;
111+
for (let depth = 0; current && depth < 30; depth += 1) {
112+
const style = getComputedStyle(current);
113+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
114+
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
115+
current = current.parentElement || null;
116+
}
113117
const rect = element.getBoundingClientRect();
114118
if (rect.width <= 0 || rect.height <= 0) return false;
115119
const viewportWidth = typeof window !== 'undefined' && typeof window.innerWidth === 'number'
@@ -159,13 +163,21 @@ export function detectChallengeDialogInPage(options = null) {
159163
.map(id => document.getElementById(id)?.textContent || '')
160164
.join(' ');
161165
} catch {}
166+
let renderedHeadings = '';
167+
try {
168+
renderedHeadings = Array.from(
169+
element.querySelectorAll?.('h1, h2, h3, [role="heading"]') || []
170+
)
171+
.filter(visible)
172+
.map(heading => heading.textContent || '')
173+
.join(' ');
174+
} catch {}
162175
const values = [
163176
element.getAttribute?.('aria-label'),
164177
labelledBy,
165-
element.querySelector?.('h1, h2, h3, [role="heading"]')?.textContent,
178+
renderedHeadings,
166179
element.getAttribute?.('title'),
167180
element.innerText,
168-
element.textContent,
169181
];
170182
for (const value of values) {
171183
const text = String(value || '');

src/firefox/src/agent/agent.js

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2545,6 +2545,7 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
25452545
? challenge.frameId
25462546
: null,
25472547
captchaChallengeFrameUrl: challenge.frameUrl || '',
2548+
captchaChallengeVisibilityConfirmed: true,
25482549
},
25492550
{},
25502551
);
@@ -2563,6 +2564,11 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
25632564
}
25642565

25652566
const activeGate = this._captchaGateStates.get(tabId);
2567+
const treeFilter = String(toolArgs?.filter || 'all').toLowerCase();
2568+
let observedChallengeFrameId = Number.isInteger(toolResult.captchaChallengeFrameId)
2569+
? toolResult.captchaChallengeFrameId
2570+
: null;
2571+
let observedChallengeFrameUrl = String(toolResult.captchaChallengeFrameUrl || '');
25662572
let challenge = detectChallengeDialog(toolResult.pageContent, {
25672573
allowGenericFailure: !!activeGate,
25682574
});
@@ -2572,11 +2578,31 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
25722578
{ allowGenericFailure: !!activeGate },
25732579
);
25742580
}
2581+
if (
2582+
challenge
2583+
&& treeFilter !== 'visible'
2584+
&& toolResult.captchaChallengeVisibilityConfirmed !== true
2585+
) {
2586+
const visibleChallenge = await this._detectChallengeDialogBeforeMutation(tabId, {
2587+
allowGenericFailure: !!activeGate,
2588+
});
2589+
if (visibleChallenge?.label) {
2590+
challenge = detectChallengeDialog(
2591+
`dialog ${JSON.stringify(String(visibleChallenge.label).slice(0, 200))}`,
2592+
{ allowGenericFailure: !!activeGate },
2593+
);
2594+
observedChallengeFrameId = Number.isInteger(visibleChallenge.frameId)
2595+
? visibleChallenge.frameId
2596+
: null;
2597+
observedChallengeFrameUrl = String(visibleChallenge.frameUrl || '');
2598+
} else {
2599+
challenge = null;
2600+
}
2601+
}
25752602
let pageUrl = String(toolResult.currentUrl || toolResult.pageUrl || '');
25762603
if (!pageUrl) {
25772604
try { pageUrl = await this._currentUrl(tabId); } catch {}
25782605
}
2579-
const treeFilter = String(toolArgs?.filter || 'all').toLowerCase();
25802606
const requestedPage = toolArgs?.page;
25812607
const requestedMaxDepth = toolArgs?.maxDepth;
25822608
const parsedMaxDepth = Number(requestedMaxDepth);
@@ -2774,10 +2800,10 @@ Rules: no prose intro, no conclusion, no "this screenshot shows...", no layout d
27742800
key,
27752801
status: publicGate.status,
27762802
publicGate,
2777-
...(Number.isInteger(toolResult.captchaChallengeFrameId)
2803+
...(Number.isInteger(observedChallengeFrameId)
27782804
? {
2779-
challengeFrameId: toolResult.captchaChallengeFrameId,
2780-
challengeFrameUrl: String(toolResult.captchaChallengeFrameUrl || ''),
2805+
challengeFrameId: observedChallengeFrameId,
2806+
challengeFrameUrl: observedChallengeFrameUrl,
27812807
}
27822808
: {}),
27832809
});

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

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -627,11 +627,15 @@ export function detectCaptchaCandidatesInPage(scope = null) {
627627
const visibleElement = (element) => {
628628
if (!element) return false;
629629
try {
630-
const style = typeof pageWindow?.getComputedStyle === 'function'
631-
? pageWindow.getComputedStyle(element)
632-
: null;
633-
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) return false;
634-
if (element.hidden || element.getAttribute?.('aria-hidden') === 'true') return false;
630+
let current = element;
631+
for (let depth = 0; current && depth < 30; depth += 1) {
632+
const style = typeof pageWindow?.getComputedStyle === 'function'
633+
? pageWindow.getComputedStyle(current)
634+
: null;
635+
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) return false;
636+
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
637+
current = current.parentElement || null;
638+
}
635639
if (typeof element.getBoundingClientRect === 'function') {
636640
const rect = element.getBoundingClientRect();
637641
if (!rect || rect.width <= 0 || rect.height <= 0) return false;
@@ -657,13 +661,21 @@ export function detectCaptchaCandidatesInPage(scope = null) {
657661
.map(id => pageDocument.getElementById?.(id)?.textContent || '')
658662
.join(' ');
659663
} catch (_) {}
664+
let renderedHeadings = '';
665+
try {
666+
renderedHeadings = Array.from(
667+
element.querySelectorAll?.('h1, h2, h3, [role="heading"]') || []
668+
)
669+
.filter(visibleElement)
670+
.map(heading => heading.textContent || '')
671+
.join(' ');
672+
} catch (_) {}
660673
return [
661674
element.getAttribute?.('aria-label'),
662675
labelledBy,
663-
element.querySelector?.('h1, h2, h3, [role="heading"]')?.textContent,
676+
renderedHeadings,
664677
element.getAttribute?.('title'),
665678
element.innerText,
666-
element.textContent,
667679
].some(value => challengeDialogRe.test(String(value || '')));
668680
});
669681
const elementInChallengeDialog = (element) => {

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,13 @@ export function detectChallengeDialogInPage(options = null) {
107107
};
108108
const visible = (element) => {
109109
try {
110-
const style = getComputedStyle(element);
111-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
112-
if (element.hidden || element.getAttribute?.('aria-hidden') === 'true') return false;
110+
let current = element;
111+
for (let depth = 0; current && depth < 30; depth += 1) {
112+
const style = getComputedStyle(current);
113+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
114+
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
115+
current = current.parentElement || null;
116+
}
113117
const rect = element.getBoundingClientRect();
114118
if (rect.width <= 0 || rect.height <= 0) return false;
115119
const viewportWidth = typeof window !== 'undefined' && typeof window.innerWidth === 'number'
@@ -159,13 +163,21 @@ export function detectChallengeDialogInPage(options = null) {
159163
.map(id => document.getElementById(id)?.textContent || '')
160164
.join(' ');
161165
} catch {}
166+
let renderedHeadings = '';
167+
try {
168+
renderedHeadings = Array.from(
169+
element.querySelectorAll?.('h1, h2, h3, [role="heading"]') || []
170+
)
171+
.filter(visible)
172+
.map(heading => heading.textContent || '')
173+
.join(' ');
174+
} catch {}
162175
const values = [
163176
element.getAttribute?.('aria-label'),
164177
labelledBy,
165-
element.querySelector?.('h1, h2, h3, [role="heading"]')?.textContent,
178+
renderedHeadings,
166179
element.getAttribute?.('title'),
167180
element.innerText,
168-
element.textContent,
169181
];
170182
for (const value of values) {
171183
const text = String(value || '');

test/run.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4753,7 +4753,10 @@ test('CAPTCHA dialog parsing handles descendant-only labels and escaped quotes',
47534753
});
47544754

47554755
test('CAPTCHA mutation preflight ignores hidden and off-viewport verification dialogs', async () => {
4756-
for (const [build, gate] of [['chrome', CaptchaGateCh], ['firefox', CaptchaGateFx]]) {
4756+
for (const [build, gate, AgentClass] of [
4757+
['chrome', CaptchaGateCh, AgentCh],
4758+
['firefox', CaptchaGateFx, AgentFx],
4759+
]) {
47574760
const hiddenCases = [
47584761
captchaEl('div', {
47594762
role: 'dialog',
@@ -4774,6 +4777,18 @@ test('CAPTCHA mutation preflight ignores hidden and off-viewport verification di
47744777
for (const dialog of hiddenCases) {
47754778
await withCaptchaFakePage(build, [dialog], async () => {
47764779
assert.equal(gate.detectChallengeDialogInPage(), null, `${build}: inactive dialog armed the mutation preflight`);
4780+
const agent = new AgentClass({});
4781+
const observation = await agent._observeCaptchaChallenge(
4782+
1,
4783+
'get_accessibility_tree',
4784+
{
4785+
pageContent: 'dialog "Security verification" [ref_1]\n button "Dismiss" [ref_2]',
4786+
pageUrl: 'https://example.test/form',
4787+
},
4788+
{},
4789+
);
4790+
assert.equal(observation.gate, null, `${build}: hidden dialog from an all-tree read armed the gate`);
4791+
assert.equal(agent._captchaGateStates.has(1), false, `${build}: hidden all-tree dialog persisted a gate`);
47774792
});
47784793
}
47794794
await withCaptchaFakePage(build, [
@@ -4796,6 +4811,39 @@ test('CAPTCHA mutation preflight ignores hidden and off-viewport verification di
47964811
`${build}: active-gate failure context was ignored`,
47974812
);
47984813
});
4814+
4815+
const hiddenStageNodes = [
4816+
captchaEl('div', {
4817+
role: 'dialog',
4818+
innerText: 'Account details',
4819+
textContent: 'Account details Security verification',
4820+
}, [
4821+
captchaEl('h2', { textContent: 'Account details' }),
4822+
captchaEl('section', {
4823+
hidden: true,
4824+
innerText: 'Security verification',
4825+
}, [
4826+
captchaEl('h2', { textContent: 'Security verification' }),
4827+
captchaEl('div', {
4828+
class: 'g-recaptcha',
4829+
'data-sitekey': 'INACTIVE_STAGE_KEY',
4830+
}),
4831+
]),
4832+
]),
4833+
];
4834+
await withCaptchaFakePage(build, hiddenStageNodes, async () => {
4835+
assert.equal(
4836+
gate.detectChallengeDialogInPage(),
4837+
null,
4838+
`${build}: hidden CAPTCHA stage labelled the visible modal as a challenge`,
4839+
);
4840+
});
4841+
const hiddenStageCandidate = await detectCaptchaOnFakePage(build, hiddenStageNodes);
4842+
assert.equal(
4843+
hiddenStageCandidate?.dialogAssociated,
4844+
false,
4845+
`${build}: hidden CAPTCHA stage associated its widget with the visible modal`,
4846+
);
47994847
}
48004848
});
48014849

0 commit comments

Comments
 (0)