"Service worker는 깨어나서, 한 가지 하고, 다시 잠드는 프로세스야. Extension을 '대부분 잠들어 있다' 는 가정으로 설계하면, MV3의 전체 디자인이 제자리에 딸각 들어와."
Background, 다시 상상하기
MV2의 background page는 Chrome이 도는 동안 메모리에 계속 남아 있는 long-lived HTML 문서 + JavaScript runtime 이었어. 편하긴 했어. 전역에 state를 박아 두고, WebSocket을 계속 물고 있고, timer도 돌리고. 대신 비쌌지. 깔아 둔 extension 전부가 user가 쓰든 말든 메모리를 물고 있었으니까. 보안도 약했고. 오래 사는 프로세스는 한 번 뚫리면 Chrome을 다시 켤 때까지 뚫린 채로 남거든.
MV3가 persistent page를 service worker로 교체: Chrome이 필요할 때 spin up 하고 idle 하면 tear down 하는 event-driven JavaScript context. PWA를 굴리는 같은 개념을 extension의 background context로 재사용. 가볍고, 악용하기 어렵고, state를 어디에 둘지 반드시 정하게 만들어. 메모리에 기대지 말라는 거야.
Service Worker 뜯어보기
service worker는 JavaScript 파일 한 장이야. HTML도 없고, DOM도 없고, window도 없어. 자기 thread에서 돌면서 popup 이나 content script 와는 떨어져 있고. 깨어날 때마다 파일 맨 위부터 다시 실행되고, 거기서 걸어 둔 listener가 event를 받아.
window 랑 document는 못 써. localStorage도 없어. fetch는 되고, 전역은 self 야. chrome.* API는 popup 에서랑 똑같이 동작하는데 딱 하나가 달라. async로 하는 일은 worker가 쫓겨나기 전에 끝나야 해. 아니면 listener에서 true를 돌려주든지.
Service Worker Lifecycle
다섯 상태가 중요:
- Installing — Chrome이 worker를 등록하고, 파일 맨 위를 한 번 훑어.
- Activating — 등록이 끝나면서
chrome.runtime.onInstalled가 울려. - Running — event가 worker를 깨웠고 지금 일하는 중이야.
- Idle — 들어오는 event도, 아직 안 끝난 async도 없어. Chrome의 퇴출 시계가 째깍거리기 시작해.
- Evicted — Chrome이 worker를 끝냈어. 파일 최상위에 두었던 값들은 다 사라져. 그래도 listener는 살아 있어. 그건 worker 메모리가 아니라 Chrome이 따로 들고 있는 등록부에 적혀 있거든.
다음 event — 등록된 listener 발동 — 이 worker를 cold로 깨움. Chrome이 파일 위에서부터 다시 실행 (중요: listener 재등록이 chrome.runtime.onMessage.addListener(...)를 파일 위에서 다시 실행함으로써 일어남) 한 다음 event dispatch.
왜 Idle eviction이 중요해
세 가지 이유:
- 메모리 — 보통 user의 컴퓨터에서는 깔아 둔 extension의 service worker가 어느 순간에도 거의 하나도 안 돌고 있어. extension이 실제로 일할 때만 메모리가 잠깐 튀는 거지.
- 보안 — 뚫린 worker가 데이터를 빼돌리려고 무한정 앉아 있을 수가 없어. 쫓겨났다가 차갑게 다시 시작되고, 그때 Chrome의 권한 검사를 처음부터 다시 통과해야 하거든.
- 성능 — Chrome의 renderer가 항상 켜져 있는 extension 코드로 불어나지 않아.
대신 치를 값은 머릿속을 바꾸는 일이야. event 하나를 넘어서까지 메모리에 든 값을 믿지 마. 중요한 건 chrome.storage로 보내고, 잠깐 쓰고 버릴 건 잃어도 그만이라고 생각해.
ClipDeck이 이걸로 뭐 할 거야
Track 2를 지나면서 ClipDeck이 UI가 아닌 첫 자리를 갖게 돼. Lesson 2에서 manifest로 background.js를 등록하고, Lesson 3에서 event로 굴러가는 한 살이를 깊게 보고, Lesson 4에서 chrome.storage.local을 상태 저장소로 들이고, Lesson 5에서 popup과 worker 사이 메시지를 잇고, Lesson 6에서 그걸 다 묶어. chrome.tabs.onUpdated가 storage의 방문 수를 올리고, popup이 그 누적값을 보여 주는 식으로.
Track 2가 끝나면 ClipDeck은 더 이상 화면만 있는 물건이 아니야. 뒤에서 브라우저를 지켜보고 상태를 쌓기 시작하거든. CRUD의 R을 위한 몸풀기지.
embeds/chrome/background.js 처음 읽었을 때, 얼마나 작은지 놀랐어 — 총 4 KB. Per-tab context cache를 Map으로 (hot tab 한해서 Chrome이 eviction 전에 보통 wake 하니까 acceptable), 최신 payload를 side panel로 forward, request-context handler 노출. 끝. Brain은 cwkPippa 상단에 머물러. Worker는 그냥 plumbing — 그게 모든 MV3 background script의 맞는 형태야.