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

activeTab와 다른 narrow permission — 무서운 경고 회피

~11 min · activeTab, permissions, narrow-permissions, ux

Level 0Extension 입덕
0 XP0/56 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"많이 깔린 Chrome extension 중 절반쯤은 '모든 사이트' 경고를 아예 안 띄워. activeTab 처럼, 같은 문제를 훨씬 좁게 푸는 권한 몇 개만 쓰거든. 이 lesson은 그 짧은 목록이야 — <all_urls> 대신 먼저 손이 가야 할 것들."

activeTab — 일꾼

"activeTab"은 좀 특별한 권한이야. user가 아래 방법으로 extension을 부르는 그 순간, 지금 보고 있는 tab에 대한 한시적 접근을 열어 줘:

  • Toolbar icon click (chrome.action.onClicked 나 popup open).
  • 키보드 command (chrome.commands.onCommand).
  • Context menu (chrome.contextMenus.onClicked).

이렇게 열린 권한으로 할 수 있는 건 그 tab의 chrome.scripting.executeScript, 그 tab의 chrome.tabs.captureVisibleTab, 그리고 tab.urltab.title 읽기야. user가 그 tab을 다른 주소로 옮기거나 닫을 때까지만 살아 있고.

여기가 핵심이야. 설치 경고가 안 뜬다는 것. activeTab 자체는 설치 화면에 아무것도 더 얹지 않아. 여기에 'user가 뭔가를 눌렀을 때만 움직인다' 는 패턴을 붙이면, 모든 사이트를 읽는다는 무서운 경고 없이 '부를 때만 일한다' 는 성격을 얻게 돼.

activeTab vs Programmatic Injection

여기서 많이들 헷갈려. activeTab은 '어디에' 접근할지를 주고, scripting은 '무슨 API를' 쓸지를 줘. 보통 둘 다 있어야 해:

"permissions": ["activeTab", "scripting"]

이 둘만 적어 두면 SW가 user 동작 handler 안에서 지금 tab에 chrome.scripting.executeScript를 부를 수 있어. host_permissions는 필요 없고, 항상 붙어 있길 원하는 게 아니라면 content script 선언도 필요 없어.

알 가치 있는 다른 narrow permission

  • tabGroups — Chrome의 tab 그룹을 읽고 고쳐. 자기 몫의 설치 경고는 없어.
  • notifications — 데스크톱 알림을 띄울 때 chrome.notifications.create를 써. 설치 경고가 하나 붙긴 하는데 ("Show notifications") 대부분 user 한테는 별거 아니야.
  • alarms — SW를 한 번 또는 주기적으로 깨우는 예약이야. 설치 경고가 없고, SW와 함께 죽어 버리는 setInterval / setTimeout을 대신해.
  • identity — Google 로그인 OAuth 흐름이야. 설치 경고에 뜨는 건 host 접근이 아니라 나중에 요청할 범위 요약이고.
  • webNavigation — 이동 이벤트를 잘게 볼 수 있어. 대신 "Read your browsing history" 경고가 따라붙어. tabs가 덜 무서우면서 거의 같은 땅을 덮어 줘.
  • contextMenus — 이미 manifest에 있지. 경고 없어.
  • storage, commands, sidePanel, omnibox, action — 전부 조용히 지나가.

조심해서 다룰 permission

아래 것들은 하나같이 시끄러운 경고를 달고 와. 정말 필요할 때만 쓰고, optional_permissions 뒤에 숨겨 두는 걸 고민해 봐:

  • history — "Read and modify your browsing history." 정말 필요한 경우는 드물어. tabs에 tab 별 기록을 곁들이면 웬만한 건 다 돼.
  • cookies — "Read and modify cookies on all sites." clip을 모으는 extension 한테는 거의 쓸 일이 없어.
  • geolocation — "Detect your physical location." user가 놀라. 필요하면 돌아가는 중에 물어봐.
  • nativeMessaging — "Communicate with cooperating native applications." 데스크톱 프로그램이랑 얘기할 수 있게 되는 대신 공격받을 구석이 열려.
  • debugger — "Use the developer tools on any tab." 정말 고급 도구용이야. 경고가 시끄러운 게 당연하고.
  • webRequest — "Block or modify network requests." MV3가 MV2 시절보다 이걸 훨씬 조여 놨어. 좁게 써도 경고는 떠.

대체 게임

host 권한이나 무서운 API 권한을 적기 전에 스스로 물어봐. "더 좁게 가는 길은 없나?" 대개는 있어:

  • 주기적으로 뭘 돌려야 해? setInterval로 worker를 붙잡아 두지 말고 alarms를 써.
  • 사이트별 설정을 기억해야 해? cookies 말고 storage.
  • 지금 페이지를 읽어야 해? host_permissions 말고 activeTab + scripting.
  • user가 방금 어디 갔는지 알아야 해? historywebNavigation 말고 onUpdated를 붙인 tabs.
  • user가 타이핑할 때 반응해야 해? 단축키는 commands, 주소창 키워드는 omnibox. debugger는 답이 아니야.
activeTab + scripting + storage. 경고 하나 없이 실제 extension의 상당수를 덮는 삼총사야. 좁은 권한부터 손을 뻗고, 더 좁게 갈 길이 정말 없을 때만 host_permissions를 적어.
activeTab과 content_scripts.matches는 어떻게 맞물릴까. activeTab은 manifest에 적어 둔 content script를 알아서 넣어 주지 않아. chrome.scripting을 쓸 수 있게 하고 tab.url을 읽게 열어 줄 뿐이야. activeTab을 부를 때 content script도 올리고 싶으면 그 handler 안에서 chrome.scripting.executeScript를 직접 불러야 해. manifest의 content_scripts 항목은 거기 적힌 주소에서 항상 도는 별개의 물건이고, 그쪽은 activeTab과 무관하게 content_scripts.matches가 다스려.

Code

background.js — activeTab이 user-click tab의 scripting 해금·javascript
// background.js — gesture handler 안 activeTab + scripting
chrome.action.onClicked.addListener(async (tab) => {
  if (!tab.id) return;
  // activeTab + scripting 이 host_permissions 없이도 이거 허용
  const [result] = await chrome.scripting.executeScript({
    target: { tabId: tab.id },
    func: () => ({ url: location.href, title: document.title }),
  });
  console.log("[ClipDeck SW] page info:", result.result);
});
background.js — real clock boundary를 위한 alarm·javascript
// background.js — midnight is a clock boundary the worker must be woken for
function scheduleNextMidnight() {
  const next = new Date();
  next.setHours(24, 0, 0, 0);
  chrome.alarms.create("clipdeck-midnight", { when: next.getTime() });
}

chrome.runtime.onInstalled.addListener(scheduleNextMidnight);
chrome.runtime.onStartup.addListener(scheduleNextMidnight);
chrome.alarms.onAlarm.addListener((alarm) => {
  if (alarm.name !== "clipdeck-midnight") return;
  void refreshBadge();
  scheduleNextMidnight();
});

// pausedTabs is storage.session state and is cleaned on tabs.onRemoved,
// not by an hourly alarm.
manifest.json — narrow-permission ClipDeck shape·json
{
  "permissions": [
    "storage",
    "activeTab",
    "scripting",
    "sidePanel",
    "contextMenus",
    "alarms",
    "tabs"
  ],
  "host_permissions": [],
  "optional_permissions": ["downloads", "notifications"],
  "optional_host_permissions": ["https://*/*", "http://*/*"]
}

External links

Exercise

alarms를 permission에 넣고 midnight alarm으로 storage event 없이 daily badge를 다시 계산해. Test 때만 1분 뒤로 잡았다가 next-midnight 계산으로 원복해. pausedTabs는 storage.session에 남고 hourly sweep이 아니라 tabs.onRemoved로 치우는지 확인해.
Hint
host_permissions를 지웠는데도 설치 경고가 그대로면 content_scripts.matches의 ['<all_urls>'] 가 아직 살아 있는 거야. Lesson 3 처럼 좁히거나, 이 lesson 처럼 activeTab + 코드로 넣는 방식으로 옮겨. chrome.alarms.onAlarm이 안 울리면 alarm이 아예 안 만들어졌을 가능성이 커. chrome.runtime.onInstalled가 아직 안 돌았을 수 있거든. extension을 지웠다가 unpacked 폴더를 다시 올려서 onInstalled를 새로 태워 봐. 참고로 MV3의 chrome.alarms는 최소 주기가 30 초야. 그보다 짧게 적으면 말없이 올림 처리돼.

Progress

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

댓글 0

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

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