jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Build Chrome Extensions · Part 7 — Storage

chrome.storage in depth: local vs sync vs session, quotas and item limits, the onChanged event for reactive UIs, storage as the single source of truth, and migrating data shapes between versions. With an interactive storage explorer.

Because the service worker is stateless and every context is isolated, chrome.storage is the shared brain of your extension {Vì service worker không trạng thái và mọi ngữ cảnh cô lập, chrome.storagebộ não chung của extension}. It’s the one place every part can read from and write to, and the foundation of reactive, multi-context UIs {Nó là nơi duy nhất mọi phần đọc và ghi được, và nền tảng của UI phản ứng, đa-ngữ-cảnh}.

Set keys, watch the quota bar, and see onChanged fire below {Đặt key, xem thanh hạn mức, và thấy onChanged kích hoạt bên dưới}:


1. Why not localStorage? {Vì sao không dùng localStorage?}

The web’s localStorage is synchronous and unavailable in a service worker (no window) {localStorage của web là đồng bộkhông có trong service worker (không window}). chrome.storage is asynchronous, accessible from every context including the worker, and fires change events {chrome.storagebất đồng bộ, truy cập được từ mọi ngữ cảnh kể cả worker, và phát sự kiện thay đổi}. Always use chrome.storage {Luôn dùng chrome.storage}.

await chrome.storage.local.set({ theme: "dark" });
const { theme } = await chrome.storage.local.get("theme");
const all = await chrome.storage.local.get(null); // everything
await chrome.storage.local.remove("theme");
await chrome.storage.local.clear();

It needs the "storage" permission in the manifest {Cần quyền "storage" trong manifest}.


2. The three areas {Ba khu vực}

AreaSizeScopeCleared {Xóa khi}Use for {Dùng cho}
local~10 MBthis device {máy này}on uninstall {gỡ cài}most data, caches {đa số dữ liệu, cache}
sync~100 KB (8 KB/item)roams across devices {theo user qua thiết bị}on uninstallsmall user prefs {cài đặt nhỏ}
session~10 MBmemory only {chỉ bộ nhớ}on browser close {đóng trình duyệt}tokens, transient state {token, state tạm}

sync automatically roams settings to wherever the user is signed in — but it’s tiny and rate-limited, so keep it to small preferences {sync tự roam cài đặt tới nơi user đăng nhập — nhưng nó bé và giới hạn tốc độ, nên chỉ để cài đặt nhỏ}. session is the right home for secrets you don’t want written to disk {session là nhà đúng cho bí mật bạn không muốn ghi xuống đĩa}. Switch areas in the explorer to compare quotas {Đổi khu vực trong explorer để so hạn mức}.


3. onChanged — the reactive backbone {onChanged — xương sống phản ứng}

This is the feature that makes storage powerful {Đây là tính năng làm storage mạnh mẽ}. Any write fires onChanged in every context that’s listening {Bất kỳ lần ghi nào cũng kích onChangedmọi ngữ cảnh đang lắng nghe}:

chrome.storage.onChanged.addListener((changes, area) => {
  if (area === "local" && changes.theme) {
    applyTheme(changes.theme.newValue); // oldValue also available
  }
});

Instead of manually broadcasting messages to keep the popup, content scripts, and options page in sync, write once to storage and let everyone react {Thay vì phát tin nhắn thủ công để giữ popup, content script và options page đồng bộ, ghi một lần vào storage và để mọi người phản ứng}. This is the cleanest cross-context state pattern {Đây là mẫu state xuyên-ngữ-cảnh sạch nhất}.


4. Storage as the single source of truth {Storage làm nguồn sự thật duy nhất}

Combine the lessons so far {Kết hợp các bài đến giờ}:

  • The popup is a view: it reads on open, writes on change {Popup là view: đọc khi mở, ghi khi đổi}.
  • The worker is stateless: it reads from storage at the start of a handler {Worker không trạng thái: đọc từ storage đầu handler}.
  • Content scripts react to onChanged to update the page live {Content script phản ứng onChanged để cập nhật trang trực tiếp}.

Storage is the durable center; everything else is ephemeral and rebuildable from it {Storage là trung tâm bền; mọi thứ khác phù du và dựng lại được từ nó}.


5. Quotas and avoiding MAX_WRITE errors {Hạn mức và tránh lỗi MAX_WRITE}

sync limits you to ~1,800 writes/hour and 120 writes/minute {sync giới hạn ~1.800 lần ghi/giờ và 120 lần ghi/phút}. Don’t write on every keystroke — debounce {Đừng ghi mỗi phím gõ — debounce}:

let pending;
function saveDebounced(value) {
  clearTimeout(pending);
  pending = setTimeout(() => chrome.storage.sync.set({ draft: value }), 500);
}

Batch multiple keys into one set({ a, b, c }) call rather than three separate calls {Gộp nhiều key vào một lời gọi set({ a, b, c }) thay vì ba lời gọi riêng}. Watch the quota bar turn amber then red in the explorer {Xem thanh hạn mức chuyển hổ phách rồi đỏ trong explorer}.


6. Defaults and migrations {Mặc định và di trú}

Set defaults once at install, and read with a fallback so a missing key never crashes you {Đặt mặc định một lần khi cài, và đọc kèm fallback để key thiếu không bao giờ làm crash}:

// onInstalled
chrome.runtime.onInstalled.addListener(({ reason }) => {
  if (reason === "install") {
    chrome.storage.local.set({ version: 2, settings: { theme: "dark" } });
  }
  if (reason === "update") migrate();
});

// reading with a default
const { settings = { theme: "dark" } } = await chrome.storage.local.get("settings");

When you change your data shape between versions, run a migration in the update branch — read the old shape, transform, write the new one, bump a stored version {Khi đổi cấu trúc dữ liệu giữa các phiên bản, chạy di trú ở nhánh update — đọc cấu trúc cũ, chuyển đổi, ghi cấu trúc mới, tăng version đã lưu}.


7. A tiny typed wrapper {Một wrapper có kiểu nhỏ}

Centralize access so you never sprinkle raw string keys everywhere {Tập trung truy cập để không rải string key thô khắp nơi}:

interface Settings { theme: "dark" | "light"; count: number; }
const DEFAULTS: Settings = { theme: "dark", count: 0 };

export async function getSettings(): Promise<Settings> {
  const stored = await chrome.storage.local.get(DEFAULTS);
  return stored as Settings;
}
export function setSettings(patch: Partial<Settings>) {
  return chrome.storage.local.set(patch);
}

One module owns the keys, the defaults, and the types — every context imports it {Một module sở hữu key, mặc định, và kiểu — mọi ngữ cảnh import nó}.


8. Exercises {Bài tập}

1. You need an auth token that should never be written to disk and should vanish when the browser closes. Which area? {Bạn cần một token xác thực không bao giờ ghi xuống đĩa và biến mất khi đóng trình duyệt. Khu vực nào?}

Solution {Lời giải}

chrome.storage.session — in-memory, cleared on browser close {chrome.storage.session — trong bộ nhớ, xóa khi đóng trình duyệt}.

2. Your options page saves a 30 KB JSON blob to sync and gets QUOTA_BYTES_PER_ITEM errors. Why, and the fix? {Options page lưu blob JSON 30 KB vào sync và bị lỗi QUOTA_BYTES_PER_ITEM. Vì sao, và sửa thế nào?}

Solution {Lời giải}

sync caps each item at ~8 KB. Store large data in local (and only small prefs in sync) {sync giới hạn mỗi item ~8 KB. Lưu dữ liệu lớn trong local (chỉ cài đặt nhỏ trong sync}).

3. Changing a setting in the options page should instantly restyle an already-open content script. How, without messaging? {Đổi cài đặt ở options page nên restyle ngay content script đang mở. Làm sao mà không messaging?}

Solution {Lời giải}

The content script listens to chrome.storage.onChanged and reacts to the new value {Content script lắng nghe chrome.storage.onChanged và phản ứng theo giá trị mới}.

Stretch {Nâng cao}: in the explorer, switch to sync, add a large value, and watch the quota bar approach its small 100 KB limit far faster than local {trong explorer, chuyển sang sync, thêm một giá trị lớn, và xem thanh hạn mức tiến tới giới hạn 100 KB nhỏ nhanh hơn local nhiều}.


Key takeaways {Điểm chính}

  • Use chrome.storage, never localStorage — async and worker-accessible {Dùng chrome.storage, không bao giờ localStorage — bất đồng bộ và worker truy cập được}.
  • local for most data, sync for small roaming prefs, session for secrets {local cho đa số dữ liệu, sync cho cài đặt roaming nhỏ, session cho bí mật}.
  • onChanged keeps every context in sync — prefer it over manual broadcasts {onChanged giữ mọi ngữ cảnh đồng bộ — ưu tiên hơn phát thủ công}.
  • Storage is the single source of truth; everything else rebuilds from it {Storage là nguồn sự thật duy nhất; mọi thứ khác dựng lại từ nó}.
  • Debounce writes and migrate shapes between versions {Debounce lần ghi và di trú cấu trúc giữa phiên bản}.

Next up {Tiếp theo}

Part 8 — UI surfaces: the popup, options page, side panel, and the action API (badge text, icon, title) — choosing the right surface and wiring them to storage {Phần 8 — Bề mặt UI: popup, options page, side panel, và action API (chữ badge, icon, tiêu đề) — chọn đúng bề mặt và nối chúng với storage}.