Content Script 개발 가이드
작성일: 2026-04-06 Last Updated: 2026-04-06 대상: Chrome, Firefox, Edge Content Script 개발
공통 문서 참조: 보안은
../common/security.md, 아키텍처 개요는architecture.md참조
목차
1. Content Script 개요
Content Script의 실행 환경
+--------------------------------------+
| 웹 페이지 |
| |
| +-------------+ +---------------+ |
| | Page Script | | Content Script| |
| | (페이지 JS) | | (확장 JS) | |
| | | | | |
| | - 페이지 DOM | | - 페이지 DOM | |
| | 직접 접근 | | 직접 접근 | |
| | - window | | - 격리된 | |
| | (공유) | | window | |
| | - 페이지 | | - chrome.* | |
| | 전역변수 | | API 접근 | |
| +-------------+ +---------------+ |
| ↕ window.postMessage |
+--------------------------------------+
핵심 특징
| 특성 | 설명 |
|---|---|
| DOM 접근 | 호스트 페이지 DOM에 직접 접근 가능 |
| JS 격리 | 페이지 JS와 격리된 별도 JS 세계 (Isolated World) |
| API 접근 | chrome.runtime, chrome.storage 등 제한된 API |
| CSP | 호스트 페이지 CSP 영향 받음 (일부) |
| 라이프사이클 | 페이지 로드 시 주입, 네비게이션 시 소멸 |
2. 주입 패턴
2.1 선언적 주입 (Manifest)
// manifest.json
{
"content_scripts": [
{
"matches": ["https://*.github.com/*"],
"exclude_matches": ["https://github.com/settings/*"],
"js": ["content-scripts/main.js"],
"css": ["content-scripts/styles.css"],
"run_at": "document_idle",
"all_frames": false,
"match_about_blank": false
}
]
}
run_at 타이밍
| 값 | 타이밍 | 사용 시나리오 |
|---|---|---|
document_start | DOM 파싱 시작 전 | 페이지 스크립트 차단, 초기 스타일 주입 |
document_end | DOM 파싱 완료, 리소스 로드 전 | DOM 요소 접근 필요 시 |
document_idle (기본) | 모든 로드 완료 또는 DOMContentLoaded 후 | 대부분의 경우 (권장) |
2.2 프로그래밍적 주입 (Scripting API)
// entrypoints/background.ts
// 특정 이벤트 시 주입 (사용자 클릭 등)
chrome.action.onClicked.addListener(async (tab) => {
if (!tab.id) return;
// JS 파일 주입
await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content-scripts/on-demand.js'],
});
// CSS 주입
await chrome.scripting.insertCSS({
target: { tabId: tab.id },
files: ['content-scripts/on-demand.css'],
});
});
// 인라인 함수 주입 (간단한 작업)
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: (param) => {
// 이 함수는 Content Script 환경에서 실행됨
// 외부 변수 접근 불가 (직렬화됨)
document.title = `Modified: ${param}`;
},
args: ['hello'],
});
2.3 선언적 + 동적 조합 (Registered Content Scripts)
// 동적으로 Content Script 등록/해제
// 사용자 설정에 따라 특정 사이트에서만 동작
async function registerContentScript(sites: string[]): Promise<void> {
// 기존 등록 해제
try {
await chrome.scripting.unregisterContentScripts({ ids: ['dynamic-script'] });
} catch {
// 미등록 상태면 무시
}
if (sites.length === 0) return;
await chrome.scripting.registerContentScripts([
{
id: 'dynamic-script',
matches: sites.map((s) => `https://${s}/*`),
js: ['content-scripts/dynamic.js'],
css: ['content-scripts/dynamic.css'],
runAt: 'document_idle',
},
]);
}
// 설정 변경 시 재등록
chrome.storage.onChanged.addListener(async (changes) => {
if (changes.enabledSites) {
await registerContentScript(changes.enabledSites.newValue);
}
});
주입 패턴 선택 가이드
| 패턴 | 적합한 상황 | 권한 |
|---|---|---|
| 선언적 | 항상 특정 사이트에서 동작 | host_permissions |
| 프로그래밍적 | 사용자 액션 시에만 동작 | activeTab + scripting |
| 동적 등록 | 사용자 설정에 따라 사이트 변경 | host_permissions + scripting |