Skip to content

Instantly share code, notes, and snippets.

@lilBunnyRabbit
Last active January 13, 2026 13:30
Show Gist options
  • Select an option

  • Save lilBunnyRabbit/14b4dea9c0bda9178cb3a90cbdded212 to your computer and use it in GitHub Desktop.

Select an option

Save lilBunnyRabbit/14b4dea9c0bda9178cb3a90cbdded212 to your computer and use it in GitHub Desktop.
Inject safe-area-inset into browser (Emulation.setSafeAreaInsetsOverride)

Quick experiment to test CSS safe-area-inset-* properties in Chrome. Uses the experimental CDP setSafeAreaInsetsOverride method to override safe area insets on the fly.

There aren't really any other free tools that let you do this easily. Hopefully this gets built into DevTools soon, but for now this works.

Usage

bun install
chmod +x index.ts
bun link chrome-safe-inset
chrome-safe-inset

Then enter values like 50 (applies to all sides) or top bottom left right (space-separated values). Enter 0 to disable.

Visual Overlay

The tool automatically injects a visual overlay frame into each page to help you see where the safe area insets are applied. The overlay:

  • Shows a low-opacity red border (#ff00001a) around the safe area boundaries
  • Uses CSS env() functions to automatically reflect the current safe area inset values
  • Is positioned absolutely and doesn't interfere with page interaction (pointer-events: none)
  • Updates automatically when you change the inset values

You can toggle the overlay on/off using the browser console:

  • window.showInsetOverlay() - Show the overlay
  • window.hideInsetOverlay() - Hide the overlay

The overlay makes it easy to visualize how your content will be affected by safe area insets, especially useful for testing responsive designs and mobile layouts.

#!/usr/bin/env bun
import CDP from 'chrome-remote-interface';
import packageJson from './package.json' with { type: 'json' };
console.log(`~~~ chrome-safe-inset v${packageJson.version} ~~~`);
const PORT = 9222;
const USER_DATA_DIR = "ADD YOUR PATH";
const SCRIPT = `
if (!window.insetOverlay) {
const overlay = document.createElement("div");
overlay.id = "safe-area-inset-overlay";
overlay.style = "position: absolute; top: 0; left: 0; bottom: 0; right: 0; pointer-events: none; border-style: solid; border-color: #ff00001a; border-width: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); z-index: 999999;";
window.insetOverlay = overlay;
window.showInsetOverlay = () => {
if (window.insetOverlay.parentElement !== document.body) {
document.body.appendChild(window.insetOverlay);
}
}
window.hideInsetOverlay = () => {
if (window.insetOverlay.parentElement === document.body) {
document.body.removeChild(window.insetOverlay);
}
}
window.showInsetOverlay();
}
`;
/**
* Represents the safe area insets for overriding system UI regions.
*
* See:
* https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setSafeAreaInsetsOverride
* https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#type-SafeAreaInsets
*/
interface SafeAreaInsets {
/** Overrides safe-area-inset-top. */
top?: number;
/** Overrides safe-area-max-inset-top (optional). */
topMax?: number;
/** Overrides safe-area-inset-bottom. */
bottom?: number;
/** Overrides safe-area-max-inset-bottom (optional). */
bottomMax?: number;
/** Overrides safe-area-inset-left. */
left?: number;
/** Overrides safe-area-max-inset-left (optional). */
leftMax?: number;
/** Overrides safe-area-inset-right. */
right?: number;
/** Overrides safe-area-max-inset-right (optional). */
rightMax?: number;
}
const keys: (keyof SafeAreaInsets)[] = ['top', 'bottom', 'left', 'right', 'topMax', 'bottomMax', 'leftMax', 'rightMax'];
const startCdp = async () => {
console.log("[cdp] Starting CDP");
const { Emulation, Runtime } = await CDP({ port: PORT });
console.log(`[cdp] CDP started on port ${PORT}`);
console.log("[cdp] Enter safe area insets (e.g. 'top bottom left right topMax bottomMax leftMax rightMax', 'number' or '0' to disable):");
for await (const line of console) {
const trimmed = line.trim();
let insets: SafeAreaInsets;
const values = trimmed.split(/\s+/).map(v => Number.parseInt(v));
if (values.length === 1) {
const value = values[0];
insets = isNaN(value) || !value ? {} : {
top: value,
bottom: value,
left: value,
right: value,
}
} else {
insets = {};
for (let i = 0; i < values.length; i++) {
const key = keys[i];
if (!key) {
console.warn("[cdp] Ignoring extra value:", values[i]);
continue;
}
insets[key] = isNaN(values[i]) || !values[i] ? 0 : values[i];
}
}
console.log("[cdp] Applying safe area insets:", insets);
try {
// @ts-expect-error - this is still experimental
await Emulation.setSafeAreaInsetsOverride({
insets,
});
console.log("[cdp] Safe area insets applied:", insets);
await Runtime.evaluate({ expression: SCRIPT });
} catch (error) {
console.error("[cdp] Failed to apply safe area insets:", error, insets);
}
}
}
let cdpPromise: Promise<void> | null = null;
(async () => {
const proc = Bun.spawn([
"google-chrome",
`--remote-debugging-port=${PORT}`,
`--user-data-dir=${USER_DATA_DIR}`,
"--no-first-run",
"--no-default-browser-check"
], {
stdout: "pipe",
stderr: "pipe",
});
const decoder = new TextDecoder();
const readStream = async (stream: ReadableStream<Uint8Array>, color: string) => {
for await (const chunk of stream) {
const lines = decoder.decode(chunk).split('\n');
for (const line of lines) {
console.log(`${color}[browser] ${line.trimEnd()}\x1b[0m`);
// TODO: Is this string enough to guarantee that the DevTools is ready?
if (!cdpPromise && line.includes("DevTools listening on ws://")) {
cdpPromise = startCdp().catch(console.error);
}
}
}
};
// Read from both streams concurrently
await Promise.all([
readStream(proc.stdout, "\x1b[33m"),
readStream(proc.stderr, "\x1b[31m"),
]);
})();
{
"name": "chrome-safe-inset",
"version": "1.0.0",
"type": "module",
"bin": {
"chrome-safe-inset": "./index.ts"
},
"dependencies": {
"chrome-remote-interface": "^0.33.3"
}
}
@stalingino
Copy link

This is awesome. Thank yo so much.  

Some update. with latest Bun, just bun link works.

in MacOs, i had to run CHROME_BINARY="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" USER_DATA_DIR="$HOME/.cache/chrome-safe-inset" chrome-safe-inset

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment