Block Google One Tap

Block annoying and glitchy Google One Tap prompts

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Block Google One Tap
// @namespace    https://chuyanliu.github.io/
// @version      2026-5-31
// @description  Block annoying and glitchy Google One Tap prompts
// @license      GNU GPLv3
// @author       Chuyan Liu
// @match        *://*/*
// @run-at       document-start
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    const logger = (...args) => console.log(`[one tap blocker]`, ...args);

    // ------------------------ Block FedCM (modern onetap) ------------------------
    // alternatively: chrome://settings/content/federatedIdentityApi
    if (typeof navigator?.credentials?.get === 'function') {
        const originalGet = navigator.credentials.get;
        navigator.credentials.get = function (options) {
            if (options?.identity?.providers?.some?.(p => p?.configURL?.includes?.('accounts.google.com'))) {
                logger('Blocked FedCM One Tap request');
                // standard generic exception returned when user clicks x, tested chrome 148.0.7778.217
                // does not use IdentityCredentialError (https://developer.mozilla.org/en-US/docs/Web/API/IdentityCredentialError)
                return Promise.reject(new DOMException('Error retrieving a token.', 'NetworkError'));
            }
            return originalGet.apply(this, arguments);
        };
    }

    // ------------------------ Block window.google API (websites before 2026) ------------------------
    function makeAPIBlockLogger(name) {
        return function (...args) {
            logger(`Blocked API call: "google.accounts.id.${name}"`, ...args);
        };
    }

    const _googleNSAccountsID = {
        intermediate: {
            verifyParentOrigin: makeAPIBlockLogger('intermediate.verifyParentOrigin'),
            notifyParentResize: makeAPIBlockLogger('intermediate.notifyParentResize'),
            notifyParentClose: makeAPIBlockLogger('intermediate.notifyParentClose'),
            notifyParentDone: makeAPIBlockLogger('intermediate.notifyParentDone'),
            notifyParentTapOutsideMode: makeAPIBlockLogger('intermediate.notifyParentTapOutsideMode'),
        },
        cancel: makeAPIBlockLogger('cancel'),
        disableAutoSelect: makeAPIBlockLogger('disableAutoSelect'),
        initialize: makeAPIBlockLogger('initialize'),
        prompt: function (callback) {
            makeAPIBlockLogger('prompt')();
            if (typeof callback === 'function') {
                setTimeout(() => {
                    try {
                        // standard generic dismissal callback value when user clicks x. NOT TESTED, I only added this after FedCM was implemented
                        callback({
                            getMomentType: () => 'skipped',
                            isDisplayMoment: () => false,
                            isDisplayed: () => false,
                            isNotDisplayed: () => false,
                            getNotDisplayedReason: () => void 0,
                            isSkippedMoment: () => true,
                            getSkippedReason: () => 'user_cancel',
                            isDismissedMoment: () => false,
                            getDismissedReason: () => void 0
                        });
                    } catch (err) {
                        logger('error whilst running callback for prompt() api:', err);
                    }
                }, 1);
            }
        },
        PromptMomentNotification: makeAPIBlockLogger('PromptMomentNotification'),
        renderButton: makeAPIBlockLogger('renderButton'),
        revoke: makeAPIBlockLogger('revoke'),
        storeCredential: makeAPIBlockLogger('storeCredential'),
        setLogLevel: makeAPIBlockLogger('setLogLevel')
    };

    // previous version is bad, this should maintain compatibility
    Object.defineProperty(window, 'google', {
        value: new Proxy(
            {
                // if somehow google props load BEFORE this script loads, then they should remain
                ...window?.google ?? {},
                accounts: new Proxy({}, {
                    ...window?.google?.accounts ?? {},

                    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
                    get(target, property, receiver) {
                        if (property === 'id') {
                            return _googleNSAccountsID;
                        }
                        return Reflect.get(target, property, receiver);
                    },
                    set(target, property, value, receiver) {
                        if (property === 'id') {
                            return true;
                        }
                        return Reflect.set(target, property, value, receiver);
                    },
                    deleteProperty(target, property) {
                        if (property === 'id') {
                            return false;
                        }
                        return Reflect.deleteProperty(target,property);
                    }
                })
            },
            {
                get(target, property, receiver) {
                    return Reflect.get(target, property, receiver);
                },
                set(target, property, value, receiver) {
                    return Reflect.set(target, property, value, receiver);
                }
            }
        ),
        writable: true,
        configurable: false,
        enumerable: true
    });

    // ------------------------ Block Legacy Iframe ------------------------
    new MutationObserver(function (mutations) {
        for (let i = 0; i < mutations.length; i++) {
            const addedNodes = mutations[i].addedNodes;
            for (let j = 0; j < addedNodes.length; j++) {
                const node = addedNodes[j];
                if (node.tagName === 'IFRAME') {
                    if (node.src?.includes?.('accounts.google.com/gsi')) {
                        node.remove();
                        logger('Deleted legacy onetap iframe:', node);
                    }
                }
            }
        }
    }).observe(document.documentElement, { childList: true, subtree: true });
})();