{"id":51085536,"url":"https://github.com/nimitbhargava/no-popunder","last_synced_at":"2026-06-23T21:32:29.504Z","repository":{"id":361600560,"uuid":"1255063949","full_name":"nimitbhargava/no-popunder","owner":"nimitbhargava","description":"Blocks popunder ad tabs on any site without breaking logins or video. No servers, no tracking, nothing leaves your device.","archived":false,"fork":false,"pushed_at":"2026-05-31T13:06:37.000Z","size":53,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-31T13:14:41.753Z","etag":null,"topics":["ad-blocker","adblock","browser-extension","chrome-extension","chromium","content-script","javascript","manifest-v3","no-tracking","popunder","popunder-blocker","popup-blocker","privacy","privacy-focused","streaming"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nimitbhargava.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":null},"created_at":"2026-05-31T11:05:03.000Z","updated_at":"2026-05-31T13:06:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/nimitbhargava/no-popunder","commit_stats":null,"previous_names":["nimitbhargava/no-popunder"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/nimitbhargava/no-popunder","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimitbhargava%2Fno-popunder","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimitbhargava%2Fno-popunder/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimitbhargava%2Fno-popunder/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimitbhargava%2Fno-popunder/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nimitbhargava","download_url":"https://codeload.github.com/nimitbhargava/no-popunder/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nimitbhargava%2Fno-popunder/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34708272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-23T02:00:07.161Z","response_time":65,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ad-blocker","adblock","browser-extension","chrome-extension","chromium","content-script","javascript","manifest-v3","no-tracking","popunder","popunder-blocker","popup-blocker","privacy","privacy-focused","streaming"],"created_at":"2026-06-23T21:32:28.591Z","updated_at":"2026-06-23T21:32:29.499Z","avatar_url":"https://github.com/nimitbhargava.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# No Popunder\n\n[![Chrome Web Store](https://img.shields.io/badge/Chrome%20Web%20Store-Add%20to%20Chrome-4285F4?logo=googlechrome\u0026logoColor=white)](https://chromewebstore.google.com/detail/no-popunder/egnfpcniepjchocifgegplahbgmnhojn)\n\u0026nbsp;[![License: GPLv3](https://img.shields.io/badge/license-GPLv3-blue.svg)](LICENSE)\n\u0026nbsp;![Manifest V3](https://img.shields.io/badge/Manifest-V3-success)\n\u0026nbsp;![No servers](https://img.shields.io/badge/servers-none-brightgreen)\n\u0026nbsp;![No tracking](https://img.shields.io/badge/tracking-none-brightgreen)\n\u0026nbsp;![~50 KB · no deps](https://img.shields.io/badge/size-~50KB%20·%20no%20deps-lightgrey)\n\nA site-agnostic Chrome extension that stops popunder ad tabs, the junk tabs that\nopen behind the page when you click. They are worst on free streaming sites. It\nworks on any site and tries hard not to break legit pop-ups like logins, payment\nwindows, and \"open in new tab\".\n\n## Why popunders get past Chrome\n\nChrome's built-in pop-up blocker only stops pop-ups that open *without* a user\ngesture. Popunders get around that. They listen for any click and call\n`window.open(adURL)` while riding on your real click, so to Chrome it looks like\nyou opened the tab yourself. That is why you need something aimed at the\ntechnique rather than at a list of domains.\n\n## How it works\n\n`block.js` runs in every frame at `document_start` and intercepts the two ways\npopunders open tabs: `window.open`, and an injected `\u003ca target=\"_blank\"\u003e` that\ngets auto-clicked. It then decides each pop-up using two layers.\n\n- **Smart (everywhere, default).** A cross-origin pop-up is blocked unless it\n  directly followed you clicking a real link or button. A pop-up that fires from\n  clicking the video, the page, or an invisible overlay (the popunder signature)\n  is blocked. A login pop-up you opened from a real button is allowed. Pop-ups\n  with no preceding click at all, like timers and page-load pop-ups, are blocked.\n- **Strict (on `strictHosts`).** On sites that have no legit pop-ups, block every\n  cross-origin pop-up whether or not there was a gesture. `7reels.cc` is seeded;\n  add more in `block.js`. Strict also applies inside the page's player iframes,\n  matched via `ancestorOrigins`.\n\nThree things are always allowed in both layers: same-origin pop-ups, same-tab\nnavigations (`window.open(url, \"_self\")`), and any domain on your Allow list.\n\n## Screenshots\n\n![The popup blocking pop-ups: a count of pop-ups blocked on the page, every blocked ad domain with a one-click Allow, an Allowed chip, and a nudge to turn on Strict](assets/screenshot-1-hero.png)\n\n![Make any site Strict in one click: the per-site Smart/Strict toggle in the popup](assets/screenshot-2-strict.png)\n\n![The local Stats page listing the sites with the most pop-ups and the most-seen ad domains, host names only, with per-row Allow and Make-strict controls](assets/screenshot-3-stats.png)\n\n![Private by design: a summary that all data is stored locally on the device, with no servers, no tracking, no accounts, and no network requests](assets/screenshot-4-privacy.png)\n\n## Install\n\n### From the Chrome Web Store\n\n[**Add to Chrome →**](https://chromewebstore.google.com/detail/no-popunder/egnfpcniepjchocifgegplahbgmnhojn)\n\nClick Add to Chrome and browse normally. Popunders are gone and logins still\nwork. Chrome keeps it updated automatically. Remove it any time from\n`chrome://extensions`, or by right-clicking the toolbar icon.\n\n\u003e **Seeing a \"not trusted by Enhanced Safe Browsing\" notice while installing?**\n\u003e That is normal for a newly published extension, and it only shows if you have\n\u003e Enhanced Safe Browsing turned on. It is a reputation signal based on how new the\n\u003e extension and its developer are on the store, not a finding about the code, and\n\u003e it clears on its own over the first few months. Click Continue to install in the\n\u003e meantime. Everything here is open source, about 50 KB of vanilla JS with no\n\u003e servers and no network calls, so you can read every line before you trust it.\n\n\u003e Works the same in any Chromium browser: Brave, Edge, Arc, Opera, Vivaldi. Open\n\u003e the link above in any of them and install.\n\n### From source (load unpacked)\n\nTo run the latest unreleased code or hack on it:\n\n1. Clone or download this repo.\n2. Open `chrome://extensions` and turn on Developer mode (top-right toggle).\n3. Click Load unpacked and select the folder.\n4. Browse normally. Popunders are gone and logins still work.\n\nIt talks to no servers either way.\n\n\u003e **Just updated?** Whether Chrome auto-updates the store version or you reload an\n\u003e unpacked build, refresh any tabs you already had open. Chrome does not swap the\n\u003e content script in tabs that were already loaded.\n\n## Privacy\n\nNo Popunder talks to no servers. No analytics, no tracking, no remote config, no\naccount. Nothing leaves your device. It asks for the minimum it needs:\n\n- `storage`, to remember your Allow list, per-site Strict toggles, and counts.\n- `activeTab`, to show the current tab's blocked count in the popup.\n- host access (`\u003call_urls\u003e`), because popunders can fire on any site, so the\n  blocking script has to be allowed to run anywhere. It inspects `window.open`\n  and link-click behavior in the page. It never reads or transmits page content.\n\nThe only thing it keeps is a local domain tally (ad host plus page host, never\nfull URLs) that you can view or wipe under Stats. Full policy in\n[PRIVACY.md](PRIVACY.md).\n\n## Seeing what it blocked\n\nThe toolbar icon shows a red badge with the number of pop-ups blocked on the\ncurrent page, which resets on navigation. Click the icon for the per-page count,\na lifetime total, and the blocked domains grouped so repeat offenders collapse\ninto one row (`host ×N`):\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/popup.png\" width=\"340\"\n       alt=\"No Popunder popup: pop-ups blocked on this page and a lifetime total, the blocked ad domains grouped (host ×N) each with a one-click Allow, an Allowed chip, and the per-site Strict toggle\"\u003e\n\u003c/p\u003e\n\nThe per-page count and its list reset on navigation. The lifetime total persists\nacross restarts, and Clear resets only the current page.\n\n### Allow or re-block a domain\n\nClick Allow next to a domain to stop blocking it everywhere. Its past entries are\nremoved and the badge drops, with no reload needed. Allowed domains show as chips\nunder Allowed. Click the × on a chip to re-block it.\n\n### Make any site Strict (one click, no editing)\n\nThe Strict on this site switch marks the current site strict, so it blocks every\ncross-origin pop-up here instead of just the popunder-signature ones. Flip it on\nfor a streaming site that still leaks and it takes effect right away, with no\nreload and no editing of `block.js`. Flip it off to go back to Smart.\n\nBuilt-in strict sites (shipped in `block.js`, for example `7reels.cc`) show the\nswitch on and disabled, since they are always strict. The toggle is stored\nlocally and synced into the blocker live.\n\n## Learning from usage (all local, no analytics)\n\nThe extension keeps a small local aggregate so you can see what is actually\nhappening and improve the shipped defaults. It does this without sending anything\nanywhere, and without Google Analytics, which cannot run under MV3 anyway and is\nthe wrong tool here.\n\nIt records domains only: the blocked ad host, and the host of the page it fired\non (for example `7reels.cc`), never the full URL, path, or query. Open it from\nthe popup footer under Stats:\n\n- Sites with the most pop-ups, which are candidates to ship in `strictHosts`.\n- Most-seen ad domains, which are candidates for a future blocklist.\n- Export JSON and Reset stats.\n\nThe same local data powers a nudge. When a page keeps getting bombed and is not\nstrict yet, the Strict on this site toggle's subtitle changes to \"⚠ N blocked\nhere. Turn on to block all pop-ups.\" so you can flip it on in one click.\n\n\u003e **Going crowd-sourced later (Phase 2).** To learn across users you would add an\n\u003e opt-in (default off) upload of just `{adHost, pageHost}` pairs to an endpoint\n\u003e you control, behind a clear privacy policy. Keep it host-only and anonymous.\n\u003e Do not ship that silently. For an ad blocker, quietly phoning home with\n\u003e browsing data is the fastest way to lose user trust and get delisted. The full\n\u003e plan (client, backend, privacy and legal, decisions, effort) is in\n\u003e [ROADMAP.md](ROADMAP.md).\n\n## Tuning (in `block.js` → `CONFIG`)\n\n- `strictHosts`, the sites to block all cross-origin pop-ups on. Add streaming\n  sites here for zero leaks: `strictHosts: ['7reels.cc', 'anothersite.to']`.\n- `gestureWindowMs`, how long after a real click a pop-up still counts as\n  user-intended in Smart mode (default 1200ms).\n- `blockWindowOpen` and `blockBlankAnchors`, to turn off a vector if you need to.\n- `logBlocks`, `false` by default. The badge and popup already show every block,\n  and console output can land on the `chrome://extensions` Errors page.\n\nReload the extension and refresh open tabs after editing.\n\n## When it gets something wrong\n\nIf a legit pop-up gets blocked (rare, on a normal site), open the popup and click\nAllow on that domain. It is remembered from then on.\n\nIf a popunder slips through on a normal site because it fired from a real button,\nflip the Strict on this site switch in the popup to block everything cross-origin\nthere. To ship a site strict by default, add it to `strictHosts` in `block.js`.\n\n## Files\n\n- `manifest.json`, the extension definition (Manifest V3, `\u003call_urls\u003e`, all\n  frames).\n- `block.js`, the Smart and Strict decision plus the blocking, in the MAIN world.\n- `relay.js`, the isolated-world bridge. It forwards blocks to the worker and\n  feeds the allowlist into `block.js` live.\n- `background.js`, the per-tab tally, badge, blocked-URL list, lifetime total,\n  and the local domain aggregate (`stats`).\n- `popup.html` and `popup.js`, the toolbar panel (count, list, Allow, Strict\n  toggle, nudge, Stats link).\n- `options.html` and `options.js`, the local Stats page (export and reset).\n- `icons/`, the toolbar icons.\n\n## Verification\n\nTested live:\n\n- Strict on `7reels.cc`: a cross-origin `window.open` with no gesture is blocked,\n  and a same-host one passes.\n- Smart on a normal page: a pop-up opened from a real button click passes, a\n  pop-up fired from clicking a `\u003cdiv\u003e` is blocked, and a pop-up with no preceding\n  click is blocked.\n- The per-domain allowlist lets an allowed domain through while others stay\n  blocked.\n\n## Contributing\n\nContributions are welcome, especially sites that still leak popunders (add them\nto `strictHosts`), legit pop-ups that got blocked, and popunders that slipped\nthrough. The whole extension is about 50 KB of dependency-free vanilla JS with no\nbuild step, so you can clone, load unpacked, edit, and reload. See\n[CONTRIBUTING.md](CONTRIBUTING.md) for setup, how to test a change by hand, and\nwhat to put in a report.\n\n## Support\n\nQuestions, bug reports, or a site that still leaks popunders: open an issue at\n[github.com/nimitbhargava/no-popunder/issues](https://github.com/nimitbhargava/no-popunder/issues).\n\n## License\n\n[GPL-3.0](LICENSE) © No Popunder contributors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnimitbhargava%2Fno-popunder","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnimitbhargava%2Fno-popunder","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnimitbhargava%2Fno-popunder/lists"}