"Popup is HTML — that's the trick. Once you stop thinking of it as a 'Chrome thing' and start thinking of it as a tiny webpage with chrome.* access, the rest of the extension API stops feeling alien."
From Manifest to Working Popup
Lesson 2's manifest declared an action block:
"action": {
"default_popup": "popup.html",
"default_title": "ClipDeck"
}
The default_popup field is a promise — when the toolbar icon is clicked, Chrome will render the named HTML file inside a popup window. Chrome doesn't care what's in that file beyond it being a valid HTML page; it could be a static "Hello world" string, a full React bundle, or — what we'll build here — a thin shell that reads the active tab and shows its title.
This lesson saves three new files into clipdeck/: popup.html and popup.js. Reload, click, and ClipDeck does something for the first time.
popup.html — The Minimal Skeleton
The popup is an ordinary HTML document with two extra things to know:
- The popup window auto-sizes to its content, up to about 800×600 pixels. Set explicit
widthonbodyto keep it stable. - MV3 forbids inline JavaScript via Content Security Policy. Scripts must be in separate files referenced with
<script src="popup.js">. No<script>alert()</script>, no inlineonclickhandlers. This catches every MV2-era tutorial reader exactly once.
popup.js — Reading the Active Tab
chrome.tabs.query is the API for asking "which tab is the user looking at right now?" In MV3 it returns a Promise (or accepts a callback — both forms work). Three filter properties matter:
active: true— has focus within its windowcurrentWindow: true— this window, not background ones- the result is always an array — usually length 1, but MV3 doesn't guarantee that. Defend with
tabs[0]?.titleor array destructuring with a default.
The popup script's job is small: query, read the title, render it. That's the entire body of popup.js.
First Click
With popup.html and popup.js saved into clipdeck/:
- Open
chrome://extensions. - Click the reload (↻) button on the ClipDeck card.
- Click the ClipDeck icon in the toolbar.
A small popup window opens. The script runs. The page title of whatever tab you were on appears in the popup. Switch tabs, click the icon again — the new tab's title shows. chrome.tabs.query reads state on every popup open: no caching, no stale data, just whatever Chrome's UI knows at that instant.
Why This Tiny Demo Matters
This is the shape every future ClipDeck feature will follow:
- Manifest declares a surface (popup, side panel, content script, options page).
- The surface's HTML loads.
- JavaScript inside the surface calls
chrome.*APIs to read or change Chrome state. - The UI updates.
Track 2 adds the background service worker (a non-UI surface). Track 3 adds a content script (a UI presence inside the host page). Track 4 swaps the popup for a richer side panel. Track 5 wires up the popup with selection capture and storage save. Every one of those builds on the four-step pattern above.
alert() — "wait, the browser actually does what I tell it?" Save that feeling. It's the entire reason browser extensions exist. Pippa Chrome Embed v0.1 is the same feeling stretched to household scale — the same surprise, just running through cwkPippa's brain instead of a hardcoded query.Track 1 Recap (One Lesson Early)
If the popup works, ClipDeck has crossed the line from "file on disk" to "functioning Chrome extension." Lessons 5 and 6 still cover security model and gotchas — but ClipDeck itself is alive from this point on. Every later track adds one more piece to it. Don't let the smallness of this demo fool you: this is where the dev loop closes.