Skip to main content

glass/browser/session/
consent.rs

1//! Bounded consent-wall assistance for common, visible UX frameworks.
2
3use super::*;
4
5/// Result of a consent-wall dismissal attempt.
6#[derive(Debug, Clone, Copy, Serialize)]
7#[serde(rename_all = "snake_case")]
8pub enum ConsentDismissalOutcome {
9    Dismissed,
10    NoConsentFound,
11    UnrecognizedFramework,
12}
13
14const CONSENT_DISMISS_FUNCTION: &str = r#"function() {
15    const visible = (el) => {
16        if (!el) return false;
17        const style = getComputedStyle(el);
18        const rect = el.getBoundingClientRect();
19        return style.display !== 'none' && style.visibility !== 'hidden' &&
20            Number(style.opacity) !== 0 && rect.width > 0 && rect.height > 0;
21    };
22    const click = (root, selectors) => {
23        for (const selector of selectors) {
24            const el = root.querySelector(selector);
25            if (visible(el)) { el.click(); return true; }
26        }
27        return false;
28    };
29    const oneTrust = document.querySelector('#onetrust-banner-sdk, #onetrust-consent-sdk, .onetrust-pc-dark-filter');
30    if (oneTrust) {
31        return JSON.stringify({framework:'onetrust', dismissed: click(document, [
32            '#onetrust-accept-btn-handler', '#onetrust-reject-all-handler'
33        ])});
34    }
35    const cookiebot = document.querySelector('#CybotCookiebotDialog, [data-template="cookiebot"]');
36    if (cookiebot) {
37        return JSON.stringify({framework:'cookiebot', dismissed: click(cookiebot, [
38            '#CybotCookiebotDialogBodyLevelButtonAccept', '#CybotCookiebotDialogBodyButtonDecline'
39        ])});
40    }
41    return JSON.stringify({framework:null, dismissed:false});
42}"#;
43
44impl BrowserSession {
45    /// Dismiss a visible OneTrust/Cookiebot consent control, if recognized.
46    /// This only clicks documented consent controls; it does not bypass bot
47    /// protection or search arbitrary page buttons.
48    pub async fn dismiss_consent(&self) -> BrowserResult<ConsentDismissalOutcome> {
49        self.policy.require(PolicyCapability::ConsentDismissal)?;
50        let raw = self.evaluate_value(CONSENT_DISMISS_FUNCTION).await?;
51        let value = raw.as_str().unwrap_or_default();
52        let parsed: Value = serde_json::from_str(value).unwrap_or_default();
53        let outcome = match (
54            parsed["framework"].as_str(),
55            parsed["dismissed"].as_bool().unwrap_or(false),
56        ) {
57            (_, true) => ConsentDismissalOutcome::Dismissed,
58            (Some("onetrust" | "cookiebot"), false) => {
59                ConsentDismissalOutcome::UnrecognizedFramework
60            }
61            (None, false) => ConsentDismissalOutcome::NoConsentFound,
62            _ => ConsentDismissalOutcome::UnrecognizedFramework,
63        };
64        if matches!(outcome, ConsentDismissalOutcome::Dismissed) {
65            self.invalidate_observation();
66            self.record_audit("dismiss_consent", "recognized_framework");
67        }
68        Ok(outcome)
69    }
70}