$AdSenseSim

// docs

Documentation

Complete reference for @codepenguin/adsense-simulator — a lightweight browser-only tool that mirrors the AdSense runtime for development and testing.

Overview

The simulator reproduces key AdSense runtime behaviors entirely in the browser with no external network requests. It detects .adsbygoogle slots, intercepts the adsbygoogle.push() queue, renders mock ads with real debug metadata, and handles SPA navigation and bfcache restoration automatically.

⚠️ Development use only. Do not use in production.

Installation

npm

bash
npm install @codepenguin/adsense-simulator

CDN

html
<script
  src="https://cdn.jsdelivr.net/npm/@codepenguin/adsense-simulator/dist/adsense-simulator.min.js"
></script>
CDNs (jsDelivr, unpkg) strip query strings. Always use the data-remove-google-ads="true" attribute — never a query string parameter.

Quick Start

Import in your development entry point:

js
import "@codepenguin/adsense-simulator";

Then use standard AdSense markup. The simulator handles the rest:

html
<ins
  class="adsbygoogle"
  style="display:block;width:300px;height:250px"
  data-ad-client="ca-pub-demo"
  data-ad-slot="123456"
></ins>

<script>
  (adsbygoogle = window.adsbygoogle || []).push({});
</script>

The simulator renders a mock ad showing: client, slot, size, and format.

Data Attributes

AttributeRequiredDescription
data-ad-clientYesAdSense publisher ID (e.g. ca-pub-demo)
data-ad-slotYesAd slot ID
data-ad-formatNo"auto" for responsive, omit for fixed
data-ad-layoutNo"in-article" or "in-feed" for layout ads
data-full-width-responsiveNo"true" to use fluid full-width sizing
data-remove-google-adsNo"true" on the CDN <script> tag to block real AdSense

Ad Sizes

All 12 standard IAB sizes supported:

SizeName
970×250Billboard
970×90Large Leaderboard
728×90Leaderboard
468×60Banner
336×280Large Rectangle
300×600Half Page
300×250Medium Rectangle
320×100Large Mobile Banner
320×50Mobile Banner
234×60Half Banner
160×600Wide Skyscraper
120×600Skyscraper

Fixed slots: set explicit width and height in the inline style. The simulator uses those dimensions directly.

Responsive Ads

Set data-ad-format="auto" to let the simulator pick the best size based on container width:

html
<ins
  class="adsbygoogle"
  style="display:block"
  data-ad-client="ca-pub-demo"
  data-ad-slot="123456"
  data-ad-format="auto"
></ins>
Container widthChosen size
< 400px320×50 (Mobile Banner)
< 600px300×250 (Medium Rectangle)
< 900px728×90 (Leaderboard)
< 1100px970×90 (Large Leaderboard)
≥ 1100px970×250 (Billboard)

Ad Layouts

Layout ads use fluid dimensions relative to their container width:

data-ad-layoutHeight formula
in-articlecontainerWidth × 0.35
in-feedcontainerWidth × 0.30
html
<ins
  class="adsbygoogle"
  style="display:block"
  data-ad-client="ca-pub-demo"
  data-ad-slot="123456"
  data-ad-format="fluid"
  data-ad-layout="in-article"
></ins>

Queue API

The simulator installs its own window.adsbygoogle array with a custom push() interceptor. Calling (adsbygoogle = window.adsbygoogle || []).push() drains the queue and triggers a slot scan.

Internal flow:

text
adsbygoogle.push()
       ↓
queue intercept
       ↓
slot scanning (.adsbygoogle elements)
       ↓
ad rendering (mock ad injected into slot)

Dynamic Insertion

A MutationObserver watches document.documentElement (not document.body) so it survives full body replacements by SPA frameworks. Slots inserted via JavaScript are detected and rendered automatically without any extra .push() call.

js
const ad = document.createElement('ins')
ad.className = 'adsbygoogle'
ad.style.cssText = 'display:block;width:300px;height:250px'
ad.dataset.adClient = 'ca-pub-demo'
ad.dataset.adSlot   = '123456'
document.body.appendChild(ad)
// MutationObserver detects it — no .push() needed

The observer also watches attribute mutations — frameworks that insert the shell <ins> first and set data-ad-client / data-ad-slot during hydration are handled correctly.

SPA Navigation

The simulator patches history.pushState and history.replaceState and listens for the popstate event. On every route change it runs a full reinit cycle:

text
teardown (disconnect observer, reset queue, clear slots)
       ↓
startDomObserver  (re-attach on documentElement)
       ↓
initQueue         (push() hook live immediately)
       ↓
scanSlots         (render all visible slots)

pushState / replaceState — reinit is immediate because the framework mounts content synchronously.

popstate (back/forward) — reinit is deferred with multiple rAF + setTimeout passes because the browser fires the event before the framework restores the DOM.

bfcache Support

The pageshow event with event.persisted === true is the only reliable signal for back-forward cache restoration (popstate may not fire). The simulator listens for it and runs the same deferred reinit path.

Script Blocking

Add data-remove-google-ads="true" to the CDN script tag to activate all 4 blocking layers:

html
<script
  src="https://cdn.jsdelivr.net/npm/@codepenguin/adsense-simulator/dist/adsense-simulator.min.js"
  data-remove-google-ads="true"
></script>
LayerMechanismWhat it stops
1document.createElement patchIntercepts src assignment — browser never fetches
3MutationObserver fallbackCatches innerHTML injection + pre-existing scripts
4window.adsbygoogle property trapPrevents real AdSense replacing the simulator queue

Layer numbers match the source code comments — Layer 2 is intentionally absent (reserved for future use).

CSP Recommendation

The MutationObserver blocker is best-effort — it fires asynchronously, leaving a small window where the browser may have started fetching adsbygoogle.js. For guaranteed blocking at the network level, add a Content Security Policy to your dev server:

text
Content-Security-Policy: script-src 'self' 'unsafe-inline'
This prevents adsbygoogle.js from loading regardless of how it is injected — the simulator blocking layers are still useful as a defense-in-depth.

Click Simulation

Clicking any rendered mock ad opens a new tab with a debug page showing full ad metadata:

FieldValue
clientdata-ad-client value
slotdata-ad-slot value
sizeRendered width×height
formatfixed / auto / fluid
containerWidthParent element offsetWidth
pagewindow.location.pathname
timestampDate/time of click

Console Output

When the simulator initialises you will see:

text
adsense-simulator: started { removeGoogleAds: "true" }

If real AdSense is blocked:

text
adsense-simulator: blocked Google AdSense script (createElement intercept)
adsense-simulator: blocked Google AdSense script (MutationObserver)

Slot validation errors (missing attributes):

text
adsense-simulator: data-ad-client and data-ad-slot are required

Known Limitations

The MutationObserver-based blocker fires asynchronously. There is a small window where the browser may have already started fetching adsbygoogle.js before the callback fires. Use CSP for guaranteed network-level blocking.

The slotScanner retries up to 5 times over 2 seconds for slots missing data-ad-client or data-ad-slot — after that the slot is marked as errored and skipped.

Not Replicated

The simulator does not replicate Google's ad network infrastructure. The following are intentionally excluded:

  • Real ads or advertiser content
  • Auction and bidding logic
  • Advertiser targeting
  • Revenue tracking
  • Fraud detection
  • AdSense account validation

These systems run exclusively on Google's servers and cannot be replicated client-side.