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

chrome.storage — Eviction 견디는 state layer

~12 min · chrome.storage, state, async, service-worker, json

Level 0Extension 입덕
0 XP0/54 lessons0/13 achievements
0/100 XP to next level100 XP to go0% complete
"Service worker 가 깨었다 잠드는 함수라면, chrome.storage 는 그게 쓰는 메모리야. Lesson 4 는 *중요한 건 다 storage 에 살아야 한다* 는 사실과 화해하는 거."

두 storage 영역: local 과 sync

chrome.storage 는 sub-namespace 여럿 가져. 가장 자주 쓸 두 개:

  • chrome.storage.local — 머신 단위, extension uninstall 까지 영속. MV3 에서 약 10 MB quota. Promise-based async API. ClipDeck 의 clip / 방문 카운트 / 누적 data 의 기본 선택.
  • chrome.storage.sync — user 의 signed-in Chrome instance 간 sync. 총 약 100 KB, item 당 약 8 KB cap. User 선호 (theme / hotkey config) 용, 누적 data 용 아냐.

덜 일반적이지만 알아둘: chrome.storage.session (2023 추가) 는 worker eviction 견디지만 browser session 끝나면 죽음. chrome.storage.managed 는 read-only, 기업 정책으로 채워짐.

Async API

모든 chrome.storage 작업이 async. MV3 부터 Promise 반환 (옛 callback signature 도 평행 동작). 메서드 여섯 개가 거의 다 커버:

  • get(key | keys[] | undefined) — 하나, 여럿, 또는 전체 read
  • set(object) — 주어진 object 를 storage 에 merge (idempotent; 무관한 키 손 안 댐)
  • remove(key | keys[]) — key 삭제
  • clear() — 이 storage 영역 전체 wipe
  • getBytesInUse(key | keys[] | undefined) — quota 점검
  • onChanged.addListener(fn) — 어느 context 에서든 write 발생 시 반응

set 을 object 로 호출하면 key 를 merge — 전체 storage 를 대체하지 않아. 그래서 { visitCount: 5 }{ clips: [...] } 영향 없이 쓸 수 있어. Storage layer 가 feature 간 composable 한 이유.

JSON 만 가능

chrome.storage 는 값을 JSON 으로 serialize. 실용적 결과:

  • 일반 object, 배열, 문자열, 숫자, boolean, null — 다 OK.
  • Date object → 들어갈 때 ISO string 으로 변환; 나올 때 문자열 받음. 타입 잃음.
  • Map, Set{} 또는 빈 배열로 됨. 저장 전에 Object.fromEntries(map) 또는 [...set] 로 명시 변환.
  • 함수, method 가진 class instance, circular reference — silent 손상 또는 노골적 실패.

JSON-호환 평범한 shape 만 써. Exotic 타입은 들어갈 때 평범한 값으로 변환, 나올 때 재구성.

변경 listening

chrome.storage.onChanged 는 모든 context — popup / side panel / service worker / content script — 의 매 write 에 fire. Popup 이 worker 가 한 write 에 즉시 반응 가능, message passing 필요 없음.

이게 read-state update 에 chrome.runtime.sendMessage 의 대안. Popup 이 onChanged 구독, SW 가 storage write, popup 재렌더. Round-trip 없음, "worker 깨어 있나" 질문 없음.

ClipDeck 의 첫 storage round-trip

Lesson 6 가 ClipDeck 의 방문 카운터에 storage 사용. 아래 두 번째 code block 이 그 skeleton. 그게 read-do-write-read 패턴의 가장 순수한 형태. Module-level 에서 stateless. ClipDeck 의 clip 리스트 (Track 3 부터) 도 같은 형태 — storage 에서 배열 read, append, write back. Edit / delete (Track 7) 도 같은 형태 — read / mutate / write.

이 한 패턴이 내재화되면 ClipDeck 나머지는 거의 다 변형.

chrome.storage 는 ClipDeck 의 정체성이 worker eviction / browser 재시작 / Mac 재부팅 너머로 사는 layer. 중요한 건 다 여기 통과. Ephemeral 한 건 scope 안에 남아 예정대로 죽음.
디버깅 시: chrome://extensions → ClipDeck → 'Inspect views: service worker' → Application 탭 → Storage → Extension Storage → 'local'. 모든 key/value 실시간 보임, 직접 편집도 가능. onChanged listener 패턴과 결합하면 stuck-state 버그 디버깅의 가장 빠른 경로.

Code

chrome.storage.local — 기본 get/set/remove/quota·javascript
// Basic chrome.storage.local usage (run from SW DevTools console)

// Read one key with default
const { visitCount = 0 } = await chrome.storage.local.get("visitCount");

// Read several keys
const { foo, bar } = await chrome.storage.local.get(["foo", "bar"]);

// Read everything
const all = await chrome.storage.local.get();

// Write — merges into existing data
await chrome.storage.local.set({
  visitCount: visitCount + 1,
  lastVisitAt: new Date().toISOString(),
});

// Remove a key
await chrome.storage.local.remove("lastVisitAt");

// Quota check
const bytes = await chrome.storage.local.getBytesInUse();
console.log("[ClipDeck SW] storage bytes in use:", bytes);
ClipDeck 방문 카운터 미리보기 — SW write, popup read + 구독·javascript
// background.js — increment a visit counter on every completed page load
chrome.tabs.onUpdated.addListener(async (tabId, info, tab) => {
  if (info.status !== "complete" || !tab.url) return;
  const { visitCount = 0 } = await chrome.storage.local.get("visitCount");
  await chrome.storage.local.set({ visitCount: visitCount + 1 });
});

// popup.js — render the current count, and re-render on every change
async function renderCount() {
  const { visitCount = 0 } = await chrome.storage.local.get("visitCount");
  document.getElementById("count").textContent = String(visitCount);
}

chrome.storage.onChanged.addListener((changes, areaName) => {
  if (areaName === "local" && "visitCount" in changes) renderCount();
});

renderCount();

External links

Exercise

"permissions": ["storage"] 를 clipdeck/manifest.json 에 추가 (storage 는 선언 permission). Reload. SW DevTools 열기 (chrome://extensions → ClipDeck → Inspect views: service worker). Console 에 paste: await chrome.storage.local.set({ greeting: 'hello from ClipDeck SW', when: new Date().toISOString() }); console.log(await chrome.storage.local.get());. Application 탭 → Storage → Extension Storage → 'local' 로 전환. 두 key 보여야 함. Table 에서 'greeting' 값 직접 편집. Console 로 돌아와 chrome.storage.onChanged.addListener((changes, area) => console.log('[change]', area, changes)) 등록. Table 에서 값 다시 편집 — listener fire 해? (해야 함, 변경 detail 포함.)
Hint
Console 에서 await 가 'await is only valid in async functions' 로 실패하면 한 줄 wrap: (async () => { await chrome.storage.local.set({...}); console.log(await chrome.storage.local.get()); })(). 최신 Chrome DevTools 는 top-level await 보통 허용, 옛 flag-gated console 은 안 함. Application 탭에서 편집 때 onChanged 안 fire 하면 보통 listener 가 이전 SW invocation 에 등록된 거, 그게 그 후 evict 된 경우 — 현재 console session 에서 재등록.

Progress

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

댓글 0

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

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