ETC
  • Development

Chrome Extension 이해하기

2025년 02월 13일

Chrome Extension을 쓰고 있어? Chrome을 쓰는 사람이라면 대부분 여러 익스텐션을 자연스럽게 활용하고 있을 거야. 이번 글에서는 Chrome Extension 개발을 시작하기 전에 알아두면 좋은 용어실행 환경을 빠르게 훑어볼게.

개요

Chrome Extension(크롬 익스텐션)은 브라우저 UI를 커스터마이징하고, 브라우저 이벤트를 감지하거나 웹페이지를 수정해서 탐색 환경을 더 편리하게 만드는 프로그램이야.

많은 Chrome Extension들은 Chrome Web Store에서 다운로드할 수 있고, 개발자들도 계속 유용한 익스텐션을 만들고 있어.

용어 및 개념

Chrome Extension은 여러 구성 요소로 이뤄져 있고, 개발을 위해 알아야 할 용어들이 있다.

각 요소들은 익스텐션의 동작과 역할을 나누는 기준이 되기 때문에, 이걸 먼저 잡아두면 구조 이해가 훨씬 편해져.

Manifest

Manifest는 Extension의 메타데이터, 리소스 정의, 권한 선언, 실행 파일 위치 같은 걸 한 곳에서 선언하는 설정 파일이야. 모든 Extension은 manifest.json이 반드시 있어야 하고, 여기에 어떤 구성 요소를 쓰는지도 다 들어간다.

아래 예시는 구조를 보여주기 위한 샘플이야. (참고로 JSON에는 주석이 없어서, 실제 파일에는 주석을 제거해야 해)

manifest.json
{
"manifest_version": 3,
"name": "확장 프로그램",
"version": "1.0.0",
"description": "커스텀 확장 프로그램입니다.",
"icons": {
"48": "images/icon-48.png",
"128": "images/icon-128.png"
},
"permissions": ["tabs", "notifications", "storage"],
"host_permissions": ["http://*/*", "https://*/*"],
"background": {
"service_worker": "service-worker.js"
},
"action": {
"default_popup": "popup.html",
"default_icon": "icons/icon48.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["content.css"]
}
],
"options_page": "options.html",
"web_accessible_resources": [
{
"resources": ["images/*", "styles/*"],
"matches": ["<all_urls>"]
}
]
}

Manifest 파일 구조에 대한 자세한 내용은 해당 링크를 참고해줘.

그리고 manifest.json은 Chrome만이 아니라 다른 브라우저(Firefox, Opera, Edge 등) 호환을 염두에 두고 구성할 수도 있어. 다만 API/권한은 브라우저별로 차이가 있어서, 실제로는 “완전 동일”이라기보단 “공통 구조를 최대한 맞추는 느낌”에 가깝다.

Service worker

Extension에서 service worker는 Extension의 중앙 이벤트 핸들러야. 탭 이벤트를 받거나, 메시지를 처리하거나, 알림/스토리지/네트워크 제어 같은 “백그라운드 성격의 작업”을 맡는다.

service worker라고 하면 웹의 Service Worker가 먼저 떠오르는데, 둘은 목적이 꽤 다르다.

Web Service Worker

Web service worker는 웹사이트의 백그라운드에서 실행되며, 네트워크 요청을 가로채거나 캐싱해 오프라인 기능을 지원한다. 이를 통해 네트워크 상태와 관계없이 빠른 로딩과 원활한 사용자 경험을 제공할 수 있다.

navigator.serviceWorker API로 등록하고, fetch 이벤트로 요청을 제어하거나 푸시 알림/백그라운드 동기화 등을 처리한다. 이런 기능은 PWA에서 핵심이다.

Extension service worker는 Manifest V3부터 도입됐고, 기존 Background Page/Script 방식의 단점을 줄이기 위해 필요할 때만 깨워서 처리하는 구조로 바뀌었다고 보면 돼.

등록은 manifest.jsonbackground.service_worker로 한다.

manifest.json
{
"background": {
"service_worker": "service-worker.js"
}
}

Import Scripts

서비스 워커에서 여러 스크립트를 나눠 쓰고 싶다면 크게 두 가지 방식이 흔해.

ES Module을 쓰려면 background.type: "module"을 같이 선언한다.

manifest.json
{
"background": {
"service_worker": "bg-loader.js",
"type": "module"
}
}

그 다음은 일반 모듈처럼 import해서 실행하면 된다.

bg-loader.ts
import { example1 } from "./bg-1";
import { example2 } from "./bg-2";
try {
example1();
example2();
} catch (e) {
// ...
}

importScripts() 방식은 아래처럼.

bg-loader.ts
try {
importScripts("./bg-1.js");
importScripts("./bg-2.js");
} catch (e) {
// ...
}

이외에도 Extension service worker의 라이프사이클이벤트가 중요한데, 이번 글에선 전체 구조 이해가 목적이라 깊게 들어가진 않을게.

Content Scripts

Content scripts는 웹페이지 컨텍스트에서 실행되는 스크립트야. 그래서 대표적으로 DOM API에 접근할 수 있고, 일부 Extension API(i18n, storage, runtime 등)도 제한적으로 쓸 수 있다.

content-script.js
var greeting = "hola, ";
var button = document.getElementById("mybutton");
button.person_name = "Roberto";
button.addEventListener(
"click",
() => alert(greeting + button.person_name + "."),
false,
);

또 content script는 Extension의 다른 구성 요소와 메시지를 교환해서 간접적으로 더 많은 API를 쓸 수 있어. 예를 들어 content script → service worker로 메시지를 보내서 활성 탭 정보를 받아오는 흐름은 이런 식이다.

content.js
chrome.runtime.sendMessage({ action: "getTabInfo" }, function (response) {
console.log("활성화 된 탭의 정보: ", response);
});
service-worker.js
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
if (message.action === "getTabInfo") {
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
sendResponse(tabs[0]);
});
}
return true;
});

더 자세한 내용은 공식 문서의 Message passing을 참고해줘.

Inject Scripts

Content script는 정적 선언, 동적 선언, 프로그래밍 방식으로 주입할 수 있어.

먼저 정적 선언manifest.json"content_scripts"에서 지정하는 방식이다. match patterns에 해당하는 페이지에서 자동 실행된다.

manifest.json
{
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": ["styles.css"],
"js": ["content-script.js"]
}
]
}

동적 선언은 “항상 주입하면 과한데, 특정 조건에서만 필요”할 때 유용하다. Scripting API로 등록/업데이트/조회/삭제를 할 수 있다.

service-worker.js
chrome.scripting
.registerContentScripts([
{
id: "session-script",
js: ["content.js"],
persistAcrossSessions: false,
matches: ["*://example.com/*"],
runAt: "document_start",
},
])
.then(() => console.log("registration complete"))
.catch((err) => console.warn("unexpected error", err));
chrome.scripting
.updateContentScripts([
{
id: "session-script",
excludeMatches: ["*://admin.example.com/*"],
},
])
.then(() => console.log("registration updated"));
chrome.scripting
.getRegisteredContentScripts()
.then((scripts) => console.log("registered content scripts", scripts));
chrome.scripting
.unregisterContentScripts({ ids: ["session-script"] })
.then(() => console.log("un-registration complete"));

마지막으로 프로그래밍 방식은 “이벤트나 특정 상황에서만 주입”할 때 쓴다. 이 경우 주입 대상 페이지에 대한 호스트 권한이 필요하다.

manifest.json
{
"host_permissions": ["https://www.developer.chrome.com/*"]
}

또는 "activeTab"을 통해 현재 활성 탭에 대해 일시적으로 권한을 얻는 방식도 있다.

manifest.json
{
"permissions": ["activeTab", "scripting"]
}

프로그래밍 방식은 파일 형태로도 주입할 수 있고,

service-worker.js
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ["content-script.js"],
});
});

함수 형태로도 주입할 수 있다.

service-worker.js
function injectedFunction(color) {
document.body.style.backgroundColor = color;
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: injectedFunction,
args: ["orange"],
});
});

content scripts 관련 상세 내용은 Content scripts에서 확인해줘.

Toolbar Action

Toolbar Action은 사용자가 Extension 아이콘을 클릭할 때 수행되는 동작을 정의하는 요소야. 아이콘, 팝업, 배지 같은 UI 설정도 여기로 묶인다.

Action API(chrome.action API)를 쓰려면 Manifest에 "action"을 선언해야 한다.

manifest.json
{
"action": {
"default_icon": {
"16": "images/icon16.png",
"24": "images/icon24.png",
"32": "images/icon32.png"
},
"default_title": "Click Me",
"default_popup": "popup.html"
}
}

chrome.action으로 탭에 배지를 붙이거나, 스타일을 바꿔서 상태를 표현할 수도 있다.

chrome.action 메서드들은 chrome.action : Methods에서 확인해줘.

탭 상태 표현

Extension action은 탭마다 다른 상태를 가질 수 있다. 예를 들어 특정 탭에 배지 텍스트를 설정하는 코드는 아래처럼 작성할 수 있다.

function getTabId() {
/* ... */
}
function getTabBadge() {
/* ... */
}
chrome.action.setBadgeText(
{
text: getTabBadge(tabId),
tabId: getTabId(),
},
() => {
/* ... */
},
);

팝업 보여주기

Extension 클릭 시 팝업을 띄우는 건 가장 흔한 패턴이고, manifest.json에서 "default_popup"으로 정의한다.

manifest.json
{
"action": {
"default_popup": "popup.html"
}
}

Side Panel

Side panel은 Manifest V3부터 도입된 기능이고, chrome.sidePanel API로 제어한다. 사용하려면 권한을 추가해야 한다.

manifest.json
{
"permissions": ["sidePanel"]
}

모든 페이지에서 표시

모든 페이지에서 동일한 사이드 패널을 띄우려면, "side_panel.default_path"를 지정하면 된다.

manifest.json
{
"side_panel": {
"default_path": "sidepanel.html"
}
}

특정 사이트에서 표시

sidePanel.setOptions()로 특정 조건(탭/사이트)에서만 활성화하는 형태도 가능하다.

service-worker.js
await chrome.sidePanel.setOptions({
tabId,
path: "sidepanel.html",
enabled: true,
});

다른 유스 케이스/메서드는 chrome.sidePanel에서 확인해줘.

DeclarativeNetRequest

DeclarativeNetRequest는 네트워크 요청을 차단/수정/리다이렉트 같은 규칙 기반으로 제어하는 기능이야. chrome.declarativeNetRequest API를 사용하고, 필요한 권한도 있다.

manifest.json
{
"permissions": [
"declarativeNetRequest",
"declarativeNetRequestFeedback",
"declarativeNetRequestWithHostAccess"
]
}

활용하려는 메서드/기능에 따라 권한이 달라질 수 있어. 자세한 내용은 chrome.declarativeNetRequest : Permissions를 참고해줘.

DeclarativeNetRequest는 양이 방대해서, 이 글에선 “이런 구성 요소가 있다” 정도로만 두고 넘어갈게.

실행 환경

Chrome Extension과 일반적인 웹 애플리케이션은 HTML/CSS/JS를 쓴다는 점에서 비슷하지만, 실행 구조와 보안 정책, API 접근성에서 큰 차이가 있다.

실행 컨텍스트

웹 애플리케이션은 기본적으로 페이지의 메인 스크립트 컨텍스트에서 실행되고, 필요하면 Web Worker/Service Worker 같은 별도 컨텍스트를 추가한다.

반면 Chrome Extension은 웹 컨텍스트 위에 Extension 전용 컨텍스트가 더 붙는다.

이 구조 때문에, 익스텐션은 컨텍스트 간 통신(Message Passing)을 전제로 설계해야 하는 경우가 많다. 어떤 역할을 어디에 두고, 어떤 데이터는 어떤 채널로 넘길지부터 잡아야 흐름이 깔끔해진다.

보안 정책

웹은 CSP를 설정하지 않으면 eval() 같은 위험한 구문이 허용될 수도 있지만, Extension은 기본적으로 더 강한 보안 제약이 걸려 있다. 대표적으로 eval()은 기본적으로 금지되어 있고, 허용하는 설정을 임의로 열어두는 것도 제한적이다. 브라우저와 밀접하게 연결된 환경이라 보안 기준이 더 엄격하다고 보면 된다.

API 접근성

Chrome Extension은 chrome.tabs, chrome.runtime 같은 확장 전용 API를 쓸 수 있다. 반대로 일반 웹은 이런 API를 쓸 수 없고, 웹 플랫폼 표준 API만 접근 가능하다.

UI 방식도 차이가 있다. 익스텐션은 팝업/사이드패널처럼 브라우저 UI 영역에 붙거나, content script로 페이지 위에 오버레이 형태로 UI를 올리는 방식이 흔하다.

라이프사이클

웹 앱은 페이지가 닫히면(또는 탭이 사라지면) 실행이 멈추는 게 기본인데, 익스텐션은 이벤트 기반으로 “필요할 때 다시 깨어나는” 구조가 섞여 있다. 특히 MV3 service worker는 항상 떠 있는 게 아니라 이벤트에 따라 살아났다 죽는 형태라, 상태 저장/복구 관점에서도 설계가 필요하다.

정리하면, Chrome Extension은 웹 실행 환경을 기반으로 하면서도 전용 컨텍스트 + 전용 API + 강한 보안 제약이 추가된 구조다. 그래서 익스텐션 개발은 기능 구현만이 아니라 “역할 분리/통신/권한/라이프사이클”을 같이 설계해야 안정적으로 굴러간다.

참고