Skip to content

Commit b73b7d1

Browse files
committed
Honor descendant CAPTCHA visibility overrides
1 parent f8888c7 commit b73b7d1

5 files changed

Lines changed: 69 additions & 5 deletions

File tree

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,12 +627,19 @@ export function detectCaptchaCandidatesInPage(scope = null) {
627627
const visibleElement = (element) => {
628628
if (!element) return false;
629629
try {
630+
const elementStyle = typeof pageWindow?.getComputedStyle === 'function'
631+
? pageWindow.getComputedStyle(element)
632+
: null;
633+
if (
634+
elementStyle
635+
&& (elementStyle.visibility === 'hidden' || elementStyle.visibility === 'collapse')
636+
) return false;
630637
let current = element;
631638
for (let depth = 0; current && depth < 30; depth += 1) {
632639
const style = typeof pageWindow?.getComputedStyle === 'function'
633640
? pageWindow.getComputedStyle(current)
634641
: null;
635-
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) return false;
642+
if (style && (style.display === 'none' || Number(style.opacity) === 0)) return false;
636643
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
637644
current = current.parentElement || null;
638645
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,12 @@ export function detectChallengeDialogInPage(options = null) {
107107
};
108108
const visible = (element) => {
109109
try {
110+
const elementStyle = getComputedStyle(element);
111+
if (elementStyle.visibility === 'hidden' || elementStyle.visibility === 'collapse') return false;
110112
let current = element;
111113
for (let depth = 0; current && depth < 30; depth += 1) {
112114
const style = getComputedStyle(current);
113-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
115+
if (style.display === 'none' || Number(style.opacity) === 0) return false;
114116
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
115117
current = current.parentElement
116118
|| (typeof current.getRootNode === 'function' ? current.getRootNode()?.host : null)

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -627,12 +627,19 @@ export function detectCaptchaCandidatesInPage(scope = null) {
627627
const visibleElement = (element) => {
628628
if (!element) return false;
629629
try {
630+
const elementStyle = typeof pageWindow?.getComputedStyle === 'function'
631+
? pageWindow.getComputedStyle(element)
632+
: null;
633+
if (
634+
elementStyle
635+
&& (elementStyle.visibility === 'hidden' || elementStyle.visibility === 'collapse')
636+
) return false;
630637
let current = element;
631638
for (let depth = 0; current && depth < 30; depth += 1) {
632639
const style = typeof pageWindow?.getComputedStyle === 'function'
633640
? pageWindow.getComputedStyle(current)
634641
: null;
635-
if (style && (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0)) return false;
642+
if (style && (style.display === 'none' || Number(style.opacity) === 0)) return false;
636643
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
637644
current = current.parentElement || null;
638645
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,12 @@ export function detectChallengeDialogInPage(options = null) {
107107
};
108108
const visible = (element) => {
109109
try {
110+
const elementStyle = getComputedStyle(element);
111+
if (elementStyle.visibility === 'hidden' || elementStyle.visibility === 'collapse') return false;
110112
let current = element;
111113
for (let depth = 0; current && depth < 30; depth += 1) {
112114
const style = getComputedStyle(current);
113-
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity) === 0) return false;
115+
if (style.display === 'none' || Number(style.opacity) === 0) return false;
114116
if (current.hidden || current.getAttribute?.('aria-hidden') === 'true') return false;
115117
current = current.parentElement
116118
|| (typeof current.getRootNode === 'function' ? current.getRootNode()?.host : null)

test/run.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4798,6 +4798,52 @@ test('CAPTCHA preflight ignores hidden dialogs while ambiguous all-tree reads fa
47984798
);
47994799
});
48004800
}
4801+
const visibilityOverrideNodes = [
4802+
captchaEl('div', { 'data-test-visibility': 'hidden' }, [
4803+
captchaEl('div', {
4804+
role: 'dialog',
4805+
innerText: 'Security verification',
4806+
'data-test-visibility': 'visible',
4807+
}, [
4808+
captchaEl('div', {
4809+
class: 'g-recaptcha',
4810+
'data-sitekey': 'VISIBILITY_OVERRIDE_KEY',
4811+
'data-test-visibility': 'visible',
4812+
}),
4813+
captchaEl('iframe', {
4814+
src: 'https://www.google.com/recaptcha/api2/anchor?k=VISIBILITY_OVERRIDE_KEY',
4815+
'data-test-visibility': 'visible',
4816+
}),
4817+
]),
4818+
]),
4819+
];
4820+
await withCaptchaFakePage(build, visibilityOverrideNodes, async () => {
4821+
assert.equal(
4822+
gate.detectChallengeDialogInPage()?.label,
4823+
'Security verification',
4824+
`${build}: visible descendant under visibility-hidden ancestor was suppressed`,
4825+
);
4826+
const runtime = await import(pathToFileURL(
4827+
path.join(ROOT, `src/${build}/src/agent/captcha-frame-runtime.js`),
4828+
).href);
4829+
const detected = runtime.detectCaptchaCandidatesInPage({
4830+
window: {
4831+
location: globalThis.location,
4832+
innerWidth: globalThis.innerWidth,
4833+
innerHeight: globalThis.innerHeight,
4834+
getComputedStyle: globalThis.getComputedStyle,
4835+
},
4836+
document: globalThis.document,
4837+
});
4838+
const candidate = detected.candidates.find(
4839+
entry => entry.websiteKey === 'VISIBILITY_OVERRIDE_KEY',
4840+
);
4841+
assert.equal(
4842+
candidate?.visible,
4843+
true,
4844+
`${build}: visible CAPTCHA widget under visibility-hidden ancestor was demoted`,
4845+
);
4846+
});
48014847
await withCaptchaFakePage(build, [
48024848
captchaEl('div', { role: 'dialog', innerText: 'Verify you are a human' }),
48034849
], async () => {
@@ -52681,7 +52727,7 @@ async function withCaptchaFakePage(build, nodes, callback) {
5268152727
globalThis.innerHeight = 720;
5268252728
globalThis.getComputedStyle = (element) => ({
5268352729
display: element.hidden ? 'none' : 'block',
52684-
visibility: 'visible',
52730+
visibility: element.getAttribute?.('data-test-visibility') || 'visible',
5268552731
opacity: '1',
5268652732
});
5268752733
// The detector reaches for the extension API by name; give each build the

0 commit comments

Comments
 (0)