DEV Community

Cover image for I put a WebGL warp shader inside a Chrome extension so ultrawide monitors stop wasting a third of the screen
Pavel Akimov
Pavel Akimov

Posted on

I put a WebGL warp shader inside a Chrome extension so ultrawide monitors stop wasting a third of the screen

I own a 3440x1440 monitor. Roughly 90% of video on the internet is 16:9. That means for two years, a third of my expensive screen was black bars for most of the day.

The existing fixes all do linear stretch — scale the video uniformly until it fills the width. It works, and it makes every face on screen about 30% wider. Your brain never stops noticing.

So I built Ultrawider, which does a non-linear warp instead: the central ~60% of the frame maps 1:1 (mathematically untouched), and all of the stretch is absorbed by the outer edges, where peripheral vision barely registers geometry. Old TVs shipped a crude version of this idea; mine is a quintic easing curve over texture coordinates in a WebGL2 fragment shader that sits on top of the <video> element.

The interactive demo on the landing page lets you drag the curve around if you want to feel the math.

Five things I learned shipping it as MV3

1. Dynamic-imported content scripts lose chrome.storage.
crxjs loads content scripts via import(chrome.runtime.getURL(...)) — and code loaded that way has no direct access to extension APIs. Every storage call in my content script goes through sendMessage to the service worker now. It's a proxy layer nobody warns you about.

2. Chrome renders DRM video as black frames with zero errors.
Widevine content doesn't throw when you try to read pixels — you just silently get black. Detecting EME reliably means checking video.mediaKeys AND keeping a known-hosts fallback, because some players attach keys after playback starts. On DRM sites the extension falls back to a CSS crop mode instead of the shader.

3. Extension updates orphan your content scripts.
When the store pushes an update, already-open tabs keep running the OLD content script — and every chrome.runtime call from it throws Extension context invalidated. The UI looks alive; the buttons do nothing. Users assume your product is broken. We now detect it and show a "reload this tab" ribbon. This one bug generated more confused feedback than everything else combined.

4. Opt-in telemetry means your usage analytics is dead. Plan around it.
Store policies push telemetry to opt-in, and nobody opts in. Usage dashboards became fiction overnight. The numbers I actually trust now are all server-side: site visits, license pings, store install counts. If you're building an extension business, design your metrics around what the server can see.

5. Your landing page can detect your own extension — no API needed.
Web-accessible resources are probeable: the page tries to load chrome-extension://<id>/icon.png, and if it succeeds, the visitor has the extension installed. We use it to split analytics into "prospects" vs "existing users". Firefox randomizes the extension origin per install, so there it doesn't work — but for Chromium it's free segmentation.

Launch week, honestly

  • 38 installs across Chrome + Edge, ~2 a day, all organic store search
  • Product Hunt: #133 of the day, a handful of new followers. One stranger with a 32:9 confirmed faces look natural — first external validation of the core idea
  • Hacker News: couldn't post; Show HN is temporarily restricted for new accounts
  • Reddit: account suspended by sitewide filters an hour after my launch story went up. Appeal pending
  • Discord: every monitor/PC community I checked has a hard "no advertising" rule — nothing to launch there, only slow organic presence
  • 25 cold emails to monitor YouTubers: one conversion — a reviewer put the link in the description of his 32:9 monitor review. That one link came from a comment under his video, not from the email blast

The week-one pattern I didn't expect: every channel with a human gatekeeper said no before a single user could say yes. Subreddit mods declined, sitewide filters banned, HN restricted, Discord rules forbid. The two channels that let strangers judge the product directly — store search and one YouTuber's comment section — are the only ones that produced actual users.

So the next experiment is the last gatekeeper-free channel I know: short-form video. I generated a 23-second vertical short (voiceover, burned-in subs, real screencast) with an agent pipeline and I'm posting it to Shorts/TikTok/Reels. If a shader demo can't survive a swipe feed, nothing can. I'll report back.

The business model, since #showdev etiquette says disclose: free on YouTube forever, ~$3/mo for other sites (there's a license server to feed — Chrome killed its payments API in 2021, so it's all self-built: Fastify + SQLite on a single tiny Fly.io machine).

Happy to go deep on the shader math, the EME weirdness, or the license backend in the comments.

Top comments (0)