본문 바로가기
C.W.K.
Stream
Lesson 04 of 06 · published

Hello ClipDeck — 첫 동작하는 popup

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

Level 0Extension 입덕
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Popup은 HTML 이야 — 그게 트릭. 'Chrome 만의 뭔가' 라고 생각하지 말고 chrome.* 접근권 가진 작은 webpage 라고 생각하기 시작하면, 나머지 extension API도 더 이상 이질감 없어."

Manifest에서 동작하는 popup까지

Lesson 2의 manifest가 action block을 선언했어:

"action": {
  "default_popup": "popup.html",
  "default_title": "ClipDeck"
}

default_popup field가 약속이야 — toolbar icon 클릭하면 Chrome이 그 이름의 HTML 파일을 popup window 안에 렌더. Chrome은 그 파일 내용에 무관심해, 유효한 HTML page 이기만 하면 — "Hello world" 정적 문자열도 가능, React bundle도 가능, 또는 — 여기서 만들 — active tab 읽어서 title 보여주는 얇은 shell.

이 lesson은 clipdeck/에 두 새 파일을 저장해: popup.html / popup.js. 테스트 전에 manifest의 permissions"activeTab"을 넣어. 그러면 action 클릭이 현재 tab title 같은 민감한 field를 잠깐 읽을 권한을 열어. Reload, 클릭, ClipDeck이 처음으로 뭔가 해.

popup.html — 최소 skeleton

Popup은 평범한 HTML 문서야, 알아둘 두 가지만:

  • Popup window는 content에 자동 크기 조정, 약 800×600까지. body에 명시적 width 박아서 안정화.
  • MV3는 inline JavaScript 금지 (Content Security Policy 통해). Script는 별도 파일에 두고 <script src="popup.js">로 참조. <script>alert()</script> 같은 inline script 안 돼, inline onclick handler도 안 돼. 모든 MV2-시대 튜토리얼 reader가 딱 한 번씩 이걸로 걸려.

popup.js — Active tab 읽기

chrome.tabs.query가 "지금 user가 보고 있는 tab이 뭐야?" 물어보는 API. MV3 에선 Promise 반환 (또는 callback — 둘 다 됨). Filter property 셋이 중요:

  • active: true — 자기 window 안에서 focus 가짐
  • currentWindow: true — 지금 이 window, background window 아님
  • 결과는 항상 배열 — 보통 length 1, 근데 MV3가 보장하지는 않아. tabs[0]?.title 같은 식 또는 default 값 있는 array destructuring으로 방어.

Popup script가 하는 일은 작아: query / title 읽기 / 렌더. 그게 popup.js 본문 전체.

첫 클릭

popup.htmlpopup.jsclipdeck/에 저장한 다음:

  1. chrome://extensions 열기.
  2. ClipDeck 카드의 reload (↻) 버튼 클릭.
  3. Toolbar의 ClipDeck 아이콘 클릭.

작은 popup window가 열려. Script가 돌아. 그 tab의 page title이 popup에 떠. Tab 바꾸고 아이콘 다시 클릭 — 새 tab의 title 표시. chrome.tabs.query가 popup 열릴 때마다 상태 read — 캐싱 없음, stale data 없음, 그 순간 Chrome UI가 아는 거 그대로.

이 작은 데모가 왜 중요해

이게 모든 미래 ClipDeck 기능이 따를 형태:

  1. Manifest가 surface 선언 (popup / side panel / content script / options page).
  2. 그 surface의 HTML load.
  3. 그 surface 안의 JavaScript가 chrome.* API 호출 — Chrome 상태 read 또는 변경.
  4. UI update.

Track 2가 background service worker 추가 (UI 없는 surface). Track 3가 content script 추가 (host page 안의 UI 존재). Track 4가 popup을 더 풍부한 side panel로 교체. Track 5가 popup에 selection capture + storage save를 wire. 다 위 4 단계 패턴 위에 쌓는 거.

Popup은 chrome.* 접근권 가진 HTML. 트릭은 "Chrome 만의 뭔가" 가 아니라 — 추가 초능력 가진 작은 webpage. 다른 모든 surface (side panel / options / content script)도 같은 원리.
Popup이 처음으로 현재 page title 렌더하는 거 보면, 반응은 어린아이가 첫 alert() 봤을 때랑 똑같아 — "잠깐, 브라우저가 진짜 내 말 듣네?" 그 느낌 잡고 있어. browser extension이 존재하는 이유 전부. Pippa Chrome Embed v0.1이 그 같은 놀라움을 household scale로 늘린 거 — 같은 surprise, 그저 hardcoded query 대신 cwkPippa의 brain 통과해서 흐를 뿐.

Track 1의 조기 회고

Popup이 동작하면, ClipDeck은 "disk 위의 파일" 에서 "동작하는 Chrome extension" 으로 선을 넘은 거야. Lesson 5와 6이 security model과 gotchas 다루지만 — ClipDeck 자체는 이 시점부터 살아 있어. 매 미래 track이 한 조각씩 더해. 이 데모의 작음에 속지 마: 여기서 dev loop가 닫혀.

Code

clipdeck/popup.html — external script reference 가진 최소 HTML·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가 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 script 차단, 외부 파일만 허용·html
<!-- Rejected by MV3 Content Security Policy: -->
<script>alert("hi")</script>

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

External links

Exercise

manifest.json에 "permissions": ["activeTab"]을 넣고, 위 코드로 clipdeck/popup.html / clipdeck/popup.js 만들어. chrome://extensions의 ClipDeck 카드 reload. Toolbar 아이콘 클릭. Popup이 현재 tab의 title 보여줘야 함 — 어떤 tab에서 테스트했고 어떤 title 떴는지 메모. 그 다음 다른 tab 두 개 (아무 site 나) 바꿔서 다시 테스트. 매번 title 업데이트 됐어? 됐으면 ClipDeck이 chrome.tabs.query를 성공적으로 처음 쓴 거야. 안 됐으면 Errors panel이 이유 알려줘 — 보통 popup.js typo, <script src> path 안 맞음, 또는 inline JS 썼다면 CSP 위반.
Hint
Popup이 비어 보이거나 "Reading current tab…" 만 보이면, popup 자체에 DevTools 열어: popup 안에서 우클릭 → Inspect. Popup이 자기 DevTools window 갖고 — popup.js 로그랑 error가 거기 떠. 평범한 page인데 activeTab.title이 비면 manifest의 activeTab 선언과 toolbar action 클릭 뒤 query가 실행됐는지 먼저 봐. Chrome 제한 page는 여전히 읽을 수 없어.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고
💛 by 피파happy

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.