Chrome 확장 프로그램에서 Offscreen Documents 만들기: 완전 가이드
Source: Dev.to
소개
Manifest V3 로 Chrome 확장 프로그램을 개발하고 있다면 서비스 워커가 자주 종료되는 문제를 겪어봤을 것입니다. Offscreen 문서는 사용자에게 보이지 않는 숨겨진 HTML 환경을 제공하여 지속적인 상태를 유지하고 연속적인 작업을 수행할 수 있게 해줍니다.
Offscreen 문서란?
Offscreen 문서는 확장 프로그램의 백그라운드에서 실행되는 숨겨진 HTML 페이지입니다. 리소스를 절약하기 위해 짧은 수명으로 동작하는 서비스 워커와 달리, Offscreen 문서는 계속 살아 있어 다음과 같은 작업을 할 수 있습니다:
- 지속적인 데이터 구조 유지(
Map,Set등) - 대용량 데이터셋에 대한 무거운 연산 수행
- 지속적인 처리가 필요한 오디오/비디오 스트림 처리
즉, 확장 프로그램이 제어하는 보이지 않는 웹 페이지이며, 장시간 실행되는 작업을 위한 안정적인 환경을 제공합니다.
이상적인 사용 사례
- 지속적인 데이터 처리 – 서비스 워커가 중단될 때 사라지는 메모리 데이터를 유지합니다.
- 무거운 연산 – 중단 없이 CPU 집약적인 알고리즘을 실행합니다.
- 오디오/비디오 처리 – 메모리에 지속적으로 접근해야 하는 미디어를 다룹니다.
주요 제한 사항
- 호스트 웹사이트의 DOM에 접근할 수 없습니다(버튼, 콘텐츠 등).
- Chrome의 Manifest V3 확장 프로그램에서만 사용할 수 있으며 Firefox에서는 지원되지 않습니다.
- 콘텐츠 스크립트와는 별개의 격리된 컨텍스트에서 실행됩니다.
- 메시징을 위해
chrome.runtimeAPI만 사용할 수 있고,storage나tabs와 같은 다른 API는 사용할 수 없습니다.
Manifest 선언
manifest.json에 offscreen 권한을 추가합니다:
{
"manifest_version": 3,
"name": "Your Extension Name",
"version": "1.0.0",
"description": "A short description of your extension.",
"permissions": ["offscreen"],
"background": {
"service_worker": "background.js",
"type": "module"
}
}
Offscreen HTML 파일 (offscreen.html)
확장 프로그램 루트에 이 파일을 생성합니다. 사용자는 전혀 보지 못하지만 실행 환경을 제공합니다.
<!DOCTYPE html>
<html>
<head>
<title>Offscreen Document</title>
</head>
<body></body>
</html>
Offscreen 스크립트 (offscreen.js)
지속적인 작업을 처리하고 백그라운드 스크립트로부터 메시지를 수신합니다.
// Perform heavy processing or maintain persistent state
console.log('Offscreen document is live');
// Listen for messages from the background or content scripts
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'Blah_Blah') {
// Perform your processing here
// ...
// Example response
sendResponse({ result: 'processed' });
}
// Return true to keep the message channel open for async response
return true;
});
Note: Offscreen 콘솔 로그는 서비스 워커와 동일한 검사기에서 확인할 수 있습니다.
chrome://extensions/페이지를 열고 개발자 모드를 활성화한 뒤 “service worker” 링크를 클릭하면 로그를 볼 수 있습니다.
Offscreen 문서 생성 (background.js)
let creatingOffscreen = null;
async function ensureOffscreen() {
const offscreenUrl = chrome.runtime.getURL('offscreen.html');
// Check if an offscreen document already exists
if ('getContexts' in chrome.runtime) {
const contexts = await chrome.runtime.getContexts({
contextTypes: ['OFFSCREEN_DOCUMENT'],
documentUrls: [offscreenUrl],
});
if (contexts.length > 0) {
console.log('Offscreen document already exists');
return;
}
} else {
// Fallback for older Chrome versions
const clients = await self.clients.matchAll();
if (clients.some(client => client.url.includes(chrome.runtime.id))) {
return;
}
}
// Create the offscreen document if it doesn't exist
if (!creatingOffscreen) {
creatingOffscreen = chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['WORKERS'],
justification: 'Maintain persistent data structures for extension functionality',
});
await creatingOffscreen;
creatingOffscreen = null;
console.log('Offscreen document created successfully');
} else {
// Wait for an ongoing creation to finish
await creatingOffscreen;
}
}
// Ensure offscreen document on startup
chrome.runtime.onStartup.addListener(() => {
ensureOffscreen();
console.log('Extension started, offscreen document ensured');
});
// Ensure offscreen document on installation
chrome.runtime.onInstalled.addListener(() => {
ensureOffscreen();
console.log('Extension installed, offscreen document created');
});
// Handle explicit requests to ensure the offscreen document exists
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg && msg.type === 'ensure_offscreen') {
ensureOffscreen()
.then(() => sendResponse({ ok: true }))
.catch(err => sendResponse({ ok: false, error: String(err) }));
return true; // Keep channel open for async response
}
});
ensureOffscreen 작동 방식
- 존재 여부 확인 –
chrome.runtime.getContexts()(권장) 또는self.clients.matchAll()(폴백)을 사용합니다. - 중복 생성 방지 – 공유된
creatingOffscreen프라미스로 레이스 컨디션을 방지합니다. - 필요 시 생성 –
chrome.offscreen.createDocument()를 호출해url,reasons,justification을 전달합니다.
Offscreen 생성 파라미터
url– Offscreen HTML 파일의 경로.reasons– Chrome API에서 허용하는 이유 중 하나 이상(WORKERS,AUDIO_PLAYBACK,DOM_PARSER등).justification– 문서가 필요한 명확한 이유; 확장 프로그램 제출 시 검토됩니다.
Offscreen 문서 닫기
문서가 더 이상 필요 없을 때는 닫아 리소스를 해제할 수 있습니다:
async function closeOffscreen() {
try {
await chrome.offscreen.closeDocument();
console.log('Offscreen document closed');
} catch (error) {
console.error('Error closing offscreen document:', error);
}
}
백그라운드와 Offscreen 간 메시징
Chrome runtime 메시징 API를 사용합니다.
백그라운드 → Offscreen:
chrome.runtime.sendMessage(
{ type: 'Blah_Blah' },
response => {
console.log('Response from offscreen:', response);
}
);
브라우저 지원 및 특성
- Manifest 버전 – Offscreen 문서는 Manifest V3(Chrome 및 기타 Chromium 기반 브라우저)에서만 사용할 수 있습니다. Firefox에서는 지원되지 않습니다.
- DOM 접근 – 호스트 페이지의 DOM에 직접 접근할 수 없으며, 모든 데이터는 메시지를 통해 전달해야 합니다.
- 지속성 – 서비스 워커와 달리 작업 간 상태가 유지됩니다.
- 가시성 – 사용자에게 전혀 보이지 않으며 UI 요소가 표시되지 않습니다.
추가 자료
- Offscreen 문서에 대한 공식 Chrome 문서.
- 인증, 구독 처리 등을 포함한 준비된 확장 프로그램 설정을 확인하려면 extFast boilerplate를 살펴보세요.