매니페스트 버전3로 변경하기

확장앱을 매니페스트 버전2(이하 MV2)에서 매니페스트 버전3(이하 MV3)로 변경하기 위한 안내서입니다. 기존 구현에 따라 간단히 MV3로 변경할 수 있는 확장앱과 많은 부분에서 재설계가 필요한 확장앱도 있을 수 있습니다. 매니페스트 버전3 변경 점검 목록에서 어떤 변경이 필요한지 확인할 수 있습니다.

기능 요약

MV3 사용과 함께 몇 가지 새로운 기능과 변화가 있습니다:

  • 서비스 워커가 백그라운드 페이지를 대신합니다.
  • 네트워크 요청 수정은 이제 새로운 declarativeNetRequest API로 처리합니다.
  • 원격 코드는 더 이상 허용되지 않습니다.
    확장앱 패키지 내에 포함된 자바스크립트 코드만 실행할 수 있게 됩니다.
  • 많은 메서드에서 Promise 패턴을 사용할 수 있게 됩니다.
    기존 콜백 함수 패턴도 여전히 지원됩니다.
  • 그 외에도 MV3와 함께 소개된 소소한 변화들이 있습니다.

자세한 내용은 Manifest V3 Overview 문서를 참고하세요.

manifest.json 파일 수정

매니페스트 버전3를 사용하려면 매니페스트 파일을 수정해야 합니다.
간단하게는 manifest_version 항목 값을 바꾸면 되지만, 그에 따라 같이 수정해 주어야 하는 부분들이 있습니다.

매니페스트 버전

"manifest_version" 항목의 값을 바꾸는 것이 확장앱 업그레이드의 핵심입니다.
이 값이 여러분의 확장앱이 어느 버전의 기능을 사용할지 결정합니다.


// Manifest V2
{
...
"manifest_version": 2
...
}


// Manifest V3
{
...
"manifest_version": 3
...
}


서비스 워커

매니페스트 버전3에서는 이제 서비스 워커가 백그라운드 페이지를 대신합니다.
"background" 항목 하위 "service_worker"키 값으로 하나의 자바스크립트 파일 경로를 지정하여 서비스 워커를 등록할 수 있습니다.


// Manifest V2
{
...
"background": {
"scripts": [
"backgroundContextMenus.js",
"backgroundOauth.js"
],
"persistent": false
},
...
}


// Manifest V3
{
...
"background": {
"service_worker": "background.js",
"type": "module" //optional
}
...
}


매니페스트 버전3에서 여러 백그라운드 스크립트를 지정할 수 없습니다.
필요하다면 "type": "module" 속성을 이용해 서비스 워커를 ES Module로 선언하여 필요한 모듈을 가져올 수 있습니다.

호스트 권한

매니페스트 버전3에서 호스트 권한은 다른 확장앱 권한과 구분하여 정의해야 합니다.


// Manifest V2
{
...
"permissions": [
"tabs",
"bookmarks",
"http://www.blogger.com/",
],
"optional_permissions": [
"unlimitedStorage",
"*://*/*",
]
...
}


// Manifest V3
{
...
"permissions": [
"tabs",
"bookmarks"
],
"optional_permissions": [
"unlimitedStorage"
],
"host_permissions": [
"http://www.blogger.com/",
],
"optional_host_permissions": [
"*://*/*",
]
...
}


일치 패턴을 "host_permissions"에 옮기더라도 콘텐츠 스크립트에는 영향을 주지 않습니다. 콘텐츠 스크립트를 삽입할 조건(일치 패턴)은 여전히 "content_scripts.matches"에 정의해야 합니다.

콘텐츠 보안 정책

매니페스트 버전2에서 콘텐츠 보안 정책 (CSP)는 문자열로 지정했었습니다.
매니페스트 버전3에서는 각 맥락별 CSP 설정을 표현하는 객체로 정의해야 합니다.


// Manifest V2
{
...
"content_security_policy": "..."
...
}


// Manifest V3
{
...
"content_security_policy": {
"extension_pages": "...",
"sandbox": "..."
}
...
}


extension_pages: 이 정책은 HTML 파일, 서비스 워커를 포함하는 확장앱 전체에 적용됩니다.

이 페이지들은 whale-extension:// 프로토콜로 제공됩니다.
예를 들면 whale-extension://확장앱_ID/foo.html 같은 주소가 됩니다.

sandbox: 이 정책은 여러분의 확장앱이 사용하는 샌드박스 확장앱 페이지에 적용됩니다.

또한, 매니페스트 버전3에서는 extension_pages에 대해 특정 CSP 설정이 제한됩니다.
script-src, object-src, worker-src 지시자는 아래 값 중 하나만을 사용할 수 있습니다:

  • self
  • none
  • 로컬 호스트 표현, (http://localhost, http://127.0.0.1 또는 해당 도메인에 대한 포트 번호 지정)

sandbox 페이지에 대해서는 이러한 새 제한이 적용되지 않습니다.

웨일 v3.15.136 이후 버전에서 매니페스트 버전3 확장앱은 확장앱에 포함된 웹 어셈블리(WebAssembly) 파일을 사용하기 위해 CSP 설정에 wasm-unsafe-eval를 사용할 수 있습니다.

Action API 통합

매니페스트 버전2에서는 "browser_action""page_action"를 구분하여 정의했었습니다. 이 API들이 처음 등장할 때에는 서로 다른 역할을 했었지만 시간이 흐르며 구분이 무의미해졌습니다. 이제 매니페스트 버전3에서 "action" API로 통합되었습니다:


// Manifest V2

// manifest.json
{
...
"browser_action": { ... },
"page_action": { ... }
...
}

// background.js
whale.browserAction.onClicked.addListener(tab => { ... });
whale.pageAction.onClicked.addListener(tab => { ... });


// Manifest V3

// manifest.json
{
...
"action": { ... }
...
}


// background.js
whale.action.onClicked.addListener(tab => { ... });


웨일 사이드바를 위해 정의하는 "sidebar_action" 매니페스트 항목과 whale.sidebarAction API는 그대로 유지됩니다.

웹 접근 리소스

이 설정을 이용해 확장앱 내부 리소스에 접근할 수 있는 웹 사이트, 확장앱을 제한할 수 있습니다.
파일 목록을 나열하는 대신 객체 배열(array of objects)을 이용해 각 리소스별 접근 권한을 세밀하게 제어할 수 있게 되었습니다.


// Manifest V2
{
...
"web_accessible_resources": [
RESOURCE_PATHS
]
...
}


// Manifest V3
{
...
"web_accessible_resources": [{
"resources": [RESOURCE_PATHS],
"matches": [MATCH_PATTERNS],
"extension_ids": [EXTENSION_IDS],
"use_dynamic_url": boolean //optional
}]
...
}


위 예제에서 대문자로 표시된 부분의 의미는 아래와 같습니다:

  • RESOURCE_PATHS: 웹에서 접근 가능하게 허용할 리소스 경로 문자열 목록. 확장앱 루트 디렉토리를 기준으로 상대 경로로 작성.
  • MATCH_PATTERNS: 위에 지정한 RESOURCE_PATHS 경로 리소스에 접근할 수 있는 웹 사이트들의 URL 패턴 문자열 목록.
  • EXTENSION_IDS: 위에 지정한 RESOURCE_PATHS 경로 리소스에 접근할 수 있는 확장앱 ID 문자열 목록.

이전에는 "web_accessible_resources"에 설정한 리소스를 모든 웹 사이트와 확장앱에서 접근할 수 있었습니다. 이로 인해 핑거프린팅이나 의도되지 않은 리소스 접근 가능성이 있었는데 매니페스트 버전3에서의 업데이트로 각 리소스별 접근 권한을 보다 엄격하게 통제할 수 있게 되었습니다. 자세한 내용은 web accessible resources 문서를 참고하십시오.

코드 실행

매니페스트 버전3는 플랫폼 변경 및 정책 제한을 조합하여 확장앱이 검증되지 않은 자바스크립트 코드를 실행할 수 없도록 새로운 제약을 추가했습니다. 대부분의 확장앱은 이 변화에 영향받지 않겠지만 만약 여러분의 매니페스트 버전2 확장앱이 원격 코드(Remotely hosted code)를 실행하고 있거나, 문자열로 된 자바스크립트 코드를 페이지 내에 실행하고 있거나, 런타임에 eval()을 사용하고 있다면 매니페스트 버전3로 변경 시 코드 실행 전략을 수정해야 합니다.

원격 코드 제한

원격 코드(Remotely hosted code)라 함은 확장앱 패키지 내부에 포함되지 않은 모든 코드를 의미합니다.
예를 들어, 아래와 같은 코드는 원격 코드로 간주됩니다:

  • 개발자의 서버에서 가져오는 자바스크립트 코드
  • CDN에서 가져오는 라이브러리 코드
  • 런타임에 eval() 함수 인자로 제공되는 코드 문자열

매니페스트 버전3에서는 모든 확장앱 구현이 확장앱 내부에 있어야 합니다. 더 이상 원격 서버에서 가져오는 코드를 실행할 수 없습니다.
원격 코드를 가져오는 이유와 상황에 따라 적용할 수 있는 몇 가지 대안이 있습니다.

사용자 설정에 따른 기능 제어

확장앱은 원격 서버에서 JSON과 같은 설정 데이터를 가져오고 로컬에 캐시합니다.
확장앱은 캐시된 설정을 사용하여 특정 기능의 활성/비활성을 결정할 수 있습니다.

주요 구현을 원격 서비스에 두는 경우

애플리케이션 구현을 웹 서비스로 옮기고 확장앱에서는 서버를 호출하는 구조로 변경하는 것을 고려하십시오 (본질적으로 메시지 교환의 한 형태입니다). 이렇게 하면 여러분의 핵심 코드를 지키면서, 확장앱 업데이트 절차 없이 필요할 때 서비스를 최신으로 업데이트 할 수 있습니다.

서드파티 라이브러리를 사용하는 경우

React, VueJS, Bootstrap 등 외부 라이브러리를 사용하는 경우 배포용으로 최소화(minified) 된 파일을 다운로드 하여 확장앱 내부에 포함하여 사용하면 됩니다.


// Manifest V2

// popup.html
...
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
...


// Manifest V3

// popup.html
...
<script src="./react-dom.production.min.js"></script>
<link href="./bootstrap.min.css" rel="stylesheet">
...


라이브러리를 서비스 워커에서 사용하려면 두 가지 방법이 있습니다:

  • 일반적인 서비스 워커에서는 importScripts()를 사용합니다.
  • 정적 임포트(import) 구문을 사용하려면 매니페스트의 "background.type""module"로 설정하십시오.

임의 문자열 실행

매니페스트 버전2에서는 임의의 코드 문자열을 whale.tabs.executeScript()를 이용해 실행할 수 있었습니다. 매니페스트 버전3에서는 이 방법을 허용하지 않으므로 필요하다면 whale.scripting.executeScript()을 사용하여 정적 파일 혹은 함수를 삽입하는 형태로 구현을 변경해야 합니다.

Scripting API를 사용하려면 매니페스트의 "permissions""scripting" 권한을 추가해야 합니다.
이 API는 권한 경고를 표시하지 않습니다.

{
"manifest_version": 3,
"permissions": ["scripting"],
...
}

정적 파일 삽입

whale.scripting.executeScript()을 이용한 정적 파일 주입은 whale.tabs.executeScript()를 사용하던 것과 거의 동일합니다. 이전 메서드는 단일 파일만 지정할 수 있었지만, 이제 새 메서드는 파일 배열을 사용합니다.


// Manifest V2

// background.js
...
whale.tabs.executeScript({
file: 'content-script.js'
});
...

// content-script.js
...
alert('File test alert');
...


// Manifest V3

// background.js
...
async function getCurrentTab() {/* ... */}
let tab = await getCurrentTab();

whale.scripting.executeScript({
target: {tabId: tab.id},
files: ['content-script.js']
});
...

// content-script.js
...
alert('File test alert');
...


외부 라이브러리를 사용하기 위해서는 해당 파일을 확장앱 패키지 내에 두고 files 배열에 추가해야 합니다:

// background.js
...
whale.scripting.executeScript({
target: {tabId: tab.id},
files: ['jquery-min.js', 'content-script.js']
});
...

함수 삽입

funcargs 속성을 사용하면 특정 함수와 인자를 콘텐츠 스크립트처럼 주입할 수 있습니다.
다만, 주입한 함수는 처음부터 콘텐츠 스크립트에 정의되어 있던 것처럼 실행되지는 않습니다.


// Manifest V2

// background.js
...
let name = 'World!';
whale.tabs.executeScript({
code: `alert('Hello, ${name}!')`
});
...


// Manifest V3

// background.js
...
async function getCurrentTab() {/* ... */}
let tab = await getCurrentTab();

function showAlert(givenName) {
alert(`Hello, ${givenName}`);
}

let name = 'World';
whale.scripting.executeScript({
target: {tabId: tab.id},
func: showAlert,
args: [name],
});
...


위 예제에서 현재 탭 정보를 얻기 위해 사용한 getCurrentTab() 함수는 아래와 같이 구현할 수 있습니다.


// Manifest V2 (callback)

function getCurrentTab(callback) {
const queryOptions = { active: true, lastFocusedWindow: true };
whale.tabs.query(queryOptions, ([tab]) => {
// `tab` 변수는 `tabs.Tab` 인스턴스이거나 `undefined`.
callback(tab);
});
}


// Manifest V3 (promise)

async function getCurrentTab() {
const queryOptions = { active: true, lastFocusedWindow: true };
// `tab` 변수는 `tabs.Tab` 인스턴스이거나 `undefined`.
const [tab] = await whale.tabs.query(queryOptions);
return tab;
}


서비스 워커

매니페스트 버전2에서 정의하던 백그라운드 페이지는 이제 서비스 워커로 대체됩니다.
이것은 대부분의 확장앱에 영향을 주는 핵심적인 변경점입니다. 주된 차이점은 아래와 같습니다:

MV2 - 백그라운드 페이지 MV3 - 서비스 워커
설정에 따라 항상 유지될 수 있습니다. 사용하지 않으면 종료됩니다.
DOM을 갖고 액세스할 수 있습니다. DOM 액세스 할 수 없습니다.
XMLHttpRequest()를 사용할 수 있습니다. fetch()를 사용해야 합니다.

서비스 워커로 전환하는 방법은 Migrating from Background Pages to Service Workers 문서를 참고하십시오.

웨일 v2.10 이후 버전에서 매니페스트 버전2 확장앱도 서비스 워커를 사용할 수 있습니다.

네트워크 요청 수정

네트워크 요청을 수정, 차단하려는 확장앱은 Web Request API 대신 새로 추가된 Declarative Net Request API를 사용해야 합니다. 새 API는 서비스 워커의 이벤트 기반 실행 모델에서 원활히 동작하고 별도 권한 없이도 네트워크 요청을 차단할 수 있는 기능을 최대화 하도록 설계되었습니다.

매니페스트 V3 확장앱은 네트워크 요청을 차단할 수 있나요?

네트워크 요청에 개입하려는 확장앱은 Declarative Net Request API를 사용해야 합니다.

declarativeNetRequest는 어떻게 쓰나요?

요청을 직접 읽고 프로그램적으로 조작하는 대신 동작 규칙을 정의합니다. 각 규칙은 어떤 네트워크 요청에 대해 어떤 동작을 수행할지 그 조건과 동작에 관한 것입니다. 예를 들어, 특정 도메인에 대해 Cookie 헤더를 제거하고 보내도록 규정할 수 있습니다. 자세한 사용법은 whale.declarativeNetRequest API 문서를 참고하십시오.

이 기능은 광고 차단 등 네트워크 요청을 수정하는 확장앱이 실제 요청 그 자체를 읽지 않고 호스트 권한을 요구하지 않고도 그 기능을 수행할 수 있게 합니다.

조건부 권한과 declarativeNetRequest

대부분의 경우 declarativeNetRequest API는 호스트 권한을 전혀 요구하지 않습니다. 그러나 특정한 상황에서는 필요할 수 있습니다.

만약 확장앱이 네트워크 요청 경로 재지정(redirect)을 하거나 헤더 수정을 하려면 여전히 호스트 권한이 필요합니다. declarativeNetRequestWithHostAccess 권한은 항상 요청 URL과 요청을 보낸 페이지(Initiator)에 대한 호스트 권한을 요구합니다.

위와 같은 이유로 호스트 권한 부여가 필요한 확장앱이라면 핵심 기능은 이 권한 없이 동작할 수 있게 하고 부가 기능만 "optional_host_permissions" 패턴을 사용하여 구혀하는 단계적 권한 전략을 사용할 것을 권합니다. 이 접근 방법은 개인 정보 관리에 민감한 사용자들이 부가적인 호스트 권한을 주지 않으면서 확장앱의 핵심 기능을 사용할 수 있게 합니다. 이 방법으로 개발자가 콘텐트 차단과 같은 범용적인 기능을 어떠한 호스트 권한 부여도 요구하지 않고 구현할 수 있게 됩니다.

삭제되는 API들

몇몇 API들은 오랫동안 삭제될 예정deprecated인 상태로 유지되어 왔지만 매니페스트 버전3에서 아래 API들이 드디어 삭제됩니다.
여러분의 확장앱이 아직 이 API를 사용하고 있다면 매니페스트 버전3로 변경하기 위해서는 다른 API로 대체해야 합니다.

  • whale.extension.connect()
  • whale.extension.getExtensionTabs()
  • whale.extension.getURL()
  • whale.extension.sendMessage()
  • whale.extension.sendRequest()
  • whale.extension.lastError
  • whale.extension.onConnect
  • whale.extension.onMessage
  • whale.extension.onRequestExternal
  • whale.extension.onRequest
  • whale.tabs.getAllInWindow()
  • whale.tabs.getSelected()
  • whale.tabs.sendRequest()
  • whale.tabs.Tab.selected
  • whale.tabs.onActiveChanged
  • whale.tabs.onHighlightChanged
  • whale.tabs.onSelectionChanged

바코드 인식 API 사용하기 매니페스트 버전3 변경 점검 목록