C.W.K.
Stream
Lesson 01 of 05 · published

chrome.action API — Icon / title / badge / popup

~11 min · chrome.action, manifest, badge, icon, popup

Level 0Extension 입덕
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"MV3 에서 toolbar surface 가 통합: icon 하나, badge 하나, popup 하나, API 하나. Lesson 1 이 chrome.action toolbox walk — 작고, 집중되고, 흥미로운 게 바뀔 때마다 tab 별로 repaint 가능."

모두를 다스리는 한 action

MV2 가 두 경쟁 개념 가졌어:

  • browser_action — 항상-visible toolbar icon. 대부분 extension 의 default UX.
  • page_action — 현재 page 에 relevant 할 때만 나타나는 toolbar icon (bookmark star 떠올려).

MV3 가 둘 다 chrome.action 으로 통합. Toolbar 존재 원하는 모든 extension 이 manifest 에 "action" object 선언, Chrome 이 default 로 모든 tab 에 icon 표시. 옛 page-action behavior (특정 page 에서만 표시) 흉내 내려면 runtime 에 SW 에서 chrome.action.disable(tabId) / enable(tabId) 호출.

Manifest 선언

Minimum:

  • action.default_icon — toolbar 에 보이는 icon. Single string path 나 size 별 key 가진 object.
  • action.default_title — hover 시 보이는 tooltip.
  • action.default_popup — Chrome 이 click 시 여는 HTML file. SW 가 chrome.action.onClicked 통해 click 처리하게 하려면 생략.

Icon 은 16 / 32 / 48 / 128 pixel 변형. Chrome 이 display 밀도와 DevTools 설정 기반으로 맞는 거 고름. Chrome 이 single 이미지 scale 안 하고 올바르게 선택하도록 multi-key object 로 제공.

Runtime API

chrome.action 이 SW 에서 호출 가능한 작은 mutator 표면 노출:

  • setIcon({ tabId?, path }) — icon 을 global 이나 tab 별로 override.
  • setTitle({ tabId?, title }) — tooltip 변경.
  • setBadgeText({ tabId?, text }) — 작은 색깔 badge text overlay 설정. ~4 자 max 권장.
  • setBadgeBackgroundColor({ tabId?, color }) — RGB 배열이나 hex string.
  • setBadgeTextColor({ tabId?, color }) — Chrome 115+.
  • setPopup({ tabId?, popup }) — click 시 여는 HTML 변경.
  • disable(tabId?) / enable(tabId?) — icon 회색 처리 "이 page 에선 actionable 안 함" 시사.
  • getUserSettings() — user 가 icon 을 toolbar 에 pin 했는지 (vs puzzle-piece menu 에 숨김) read.

주목: 모든 mutator 가 optional tabId 받음. tabId 있으면 → 그 tab 만. tabId 없으면 → override 안 받은 모든 tab 에 global default 적용.

Click behavior

User 가 icon 클릭 시 일어나는 두 mutually-exclusive 경로:

  1. Popup mode (action.default_popup 설정 시 default). Click 이 popup 열고; chrome.action.onClicked fire 안 함.
  2. SW-handled mode (default_popup 없음). Click 이 SW 의 chrome.action.onClicked fire; 뭘 할지 결정 — side panel 열기, script 실행, message 보내기.

Runtime 에 chrome.action.setPopup({ tabId, popup: '' }) (빈 popup string 이 그 tab 의 popup disable 하고 onClicked 재활성화) 통해 둘 사이 전환 가능.

Signal 로서의 badge

Badge 가 작아 — 대부분 browser scale 에서 ~4 자 max — 한 정보 명확하게 communicate:

  • Count. 안 읽은 메시지, 오늘 저장한 clip, pending task.
  • Status. Mode 나타내는 두 글자 ("ON" / "OFF", "REC" / "").
  • Alert. 주목 필요할 때 빨간 느낌표.

빈 text (setBadgeText({ text: '' })) 가 badge clear. ClipDeck 이 오늘 clip count (Track 5 Lesson 5) 와 per-tab paused state 에 badge 사용 예정.

chrome.action 이 toolbar 의 전체 vocabulary: icon / title / badge / popup / click handler. 모든 mutator 가 per-tab 특화 위해 optional tabId 받음. Badge 를 paragraph 가 아닌 single 신호 한 조각으로 사용.
User 가 icon pin 했는지 확인. Chrome 91+ chrome.action.getUserSettings(){ isOnToolbar: boolean } 반환. False 면 icon 이 puzzle-piece menu 뒤 숨겨짐 — 첫 install 시 감지해 user 한테 pin 권유 유용. Pin 없이는 user 가 존재 잊음.

Code

manifest.json — multi-size icon 가진 action block·json
{
  "manifest_version": 3,
  "name": "ClipDeck",
  "version": "0.8.0",
  "action": {
    "default_title": "ClipDeck — save clips and browse them",
    "default_popup": "popup.html",
    "default_icon": {
      "16": "icons/16.png",
      "32": "icons/32.png",
      "48": "icons/48.png",
      "128": "icons/128.png"
    }
  },
  "background": { "service_worker": "background.js" },
  "side_panel": { "default_path": "panel.html" },
  "permissions": ["storage", "tabs", "scripting", "activeTab", "sidePanel"],
  "content_scripts": [
    { "matches": ["<all_urls>"], "js": ["content.js"], "run_at": "document_idle" }
  ]
}
background.js — clip count 가 drive 하는 badge, onChanged 통해 refresh·javascript
// background.js — 오늘 clip count 와 badge sync 유지
async function refreshBadge() {
  const { clips = [] } = await chrome.storage.local.get("clips");
  const todayStart = new Date();
  todayStart.setHours(0, 0, 0, 0);
  const todayCount = clips.filter((c) => c.savedAt >= todayStart.getTime()).length;

  await chrome.action.setBadgeBackgroundColor({ color: "#1a6bd6" });
  await chrome.action.setBadgeText({
    text: todayCount === 0 ? "" : String(todayCount > 999 ? "999+" : todayCount),
  });
  await chrome.action.setTitle({
    title: todayCount === 0
      ? "ClipDeck — no clips today"
      : `ClipDeck — ${todayCount} clip${todayCount === 1 ? "" : "s"} today`,
  });
}

chrome.runtime.onInstalled.addListener(refreshBadge);
chrome.runtime.onStartup.addListener(refreshBadge);
chrome.storage.onChanged.addListener((c, a) => {
  if (a === "local" && "clips" in c) refreshBadge();
});
background.js — onboarding 위한 first-install pin-check·javascript
// background.js — user 가 icon pin 했는지 감지 (Chrome 91+)
chrome.runtime.onInstalled.addListener(async (details) => {
  if (details.reason !== "install") return;
  try {
    const settings = await chrome.action.getUserSettings();
    if (!settings.isOnToolbar) {
      console.log("[ClipDeck SW] icon is hidden behind the puzzle-piece menu");
      // 첫 install onboarding 이 여기서 icon pin 법 설명하는 'Welcome' tab 열거나
      // 일회성 notification 표시 가능.
    }
  } catch (err) {
    // getUserSettings 는 Chrome 91+; 옛 Chrome 은 throw, 무시.
  }
});

External links

Exercise

clipdeck/manifest.json 을 첫 번째 code block (version 0.8.0) 으로 bump. clipdeck/icons/ 아래 실제 16/32/48/128 px PNG icon 생성 / 배치 — exercise 위해 single file 을 네 번 복사해도 됨; badge logic 은 신경 안 씀. 두 번째 code block (badge refresh) 을 background.js 에 추가. Reload. Ctrl+Shift+K 로 clip 저장 — toolbar icon 이 이제 '1' 표시. 몇 개 더 저장 — badge update. 내일로 전환 (또는 test 위해 todayStart 를 먼 과거 날짜로 hardcode) — badge clear. Icon hover — tooltip 이 count 표시. 선택적으로 세 번째 code block 추가하고 ClipDeck 제거 후 재설치해 console log 보기.
Hint
Clip 저장할 때 badge update 안 되면 storage.onChanged listener 가 아마 너무 늦게 attach 된 것 — SW 가 evict 됐다 clip 저장 위해 깨어났지만 listener 가 저장 완료 전 등록 안 됨. chrome.storage.onChanged.addListener(...) 를 background.js top level 에 두기, async function 안 아님. Badge 가 맞는 count 보이는데 색이 default 빨강이면 setBadgeBackgroundColor 가 await 되는지 더블 체크 (async). Icon 이 pixel 깨지면 single 파일이 upscale 되는 것 — Chrome 이 요청하는 네 size 제공.

Progress

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

댓글 0

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

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