C.W.K.
Stream
Lesson 04 of 06 · published

Hello ClipDeck — First Working Popup

~14 min · clipdeck, popup, chrome.tabs, first-working-demo, hands-on

Level 0Extension Curious
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"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 width on body to 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 inline onclick handlers. 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 window
  • currentWindow: 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]?.title or 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/:

  1. Open chrome://extensions.
  2. Click the reload (↻) button on the ClipDeck card.
  3. 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:

  1. Manifest declares a surface (popup, side panel, content script, options page).
  2. The surface's HTML loads.
  3. JavaScript inside the surface calls chrome.* APIs to read or change Chrome state.
  4. 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.

Popup is HTML with chrome.* access. The trick isn't a "Chrome thing" — it's a tiny webpage with extra superpowers. Every other surface (side panel, options, content script) works the same way underneath.
The first time you see your popup render the current page title, the reaction should be the same as a kid seeing their first 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.

Code

clipdeck/popup.html — minimal HTML with external script reference·html
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>ClipDeck</title>
  <style>
    body {
      font-family: -apple-system, system-ui, sans-serif;
      font-size: 13px;
      width: 240px;
      padding: 12px;
      margin: 0;
    }
    h1 { font-size: 14px; margin: 0 0 8px 0; }
    .title { color: #555; word-break: break-word; }
    .empty { color: #999; font-style: italic; }
  </style>
</head>
<body>
  <h1>ClipDeck</h1>
  <div id="page-title" class="empty">Reading current tab…</div>
  <script src="popup.js"></script>
</body>
</html>
clipdeck/popup.js — chrome.tabs.query reading the active tab·javascript
// clipdeck/popup.js
// Runs every time the popup opens.
// Reads the active tab in the current window, renders its title.

async function showCurrentTabTitle() {
  const [activeTab] = await chrome.tabs.query({
    active: true,
    currentWindow: true,
  });

  const titleEl = document.getElementById("page-title");
  if (activeTab?.title) {
    titleEl.textContent = activeTab.title;
    titleEl.classList.remove("empty");
    titleEl.classList.add("title");
  } else {
    titleEl.textContent = "(no active tab — open a real page and try again)";
  }
}

showCurrentTabTitle();
MV3 CSP gotcha — inline scripts blocked, external files only·html
<!-- Rejected by MV3 Content Security Policy: -->
<script>alert("hi")</script>

<!-- Allowed — external file reference only: -->
<script src="popup.js"></script>

External links

Exercise

Create clipdeck/popup.html and clipdeck/popup.js with the code above. Reload the ClipDeck card in chrome://extensions. Click the toolbar icon. The popup should display the current tab's title — write down which tab you tested on and the title that appeared. Now switch to two other tabs (any sites) and re-test. Did the title update each time? If yes, ClipDeck just used chrome.tabs.query successfully for the first time. If no, the Errors panel will tell you why — typically a typo in popup.js, a path mismatch in <script src>, or a CSP violation if you tried inline JS.
Hint
If the popup looks empty or shows only "Reading current tab…", open DevTools on the popup itself: right-click inside the popup → Inspect. The popup gets its own DevTools window where popup.js logs and errors appear. tabs[0] being undefined usually means the popup opened from chrome://extensions itself (which has no normal active tab). Test from a regular page like google.com.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue
💛 by Pippahappy

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.