크롬 익스텐션(Chrome Extension)은 모두 자바스크립트(Javascript)로 되어있기때문에 웹개발을 해본 사람이라면 그리 큰 노력을 들이지 않고도 개발을 시작 할 수 있다. 하지만 구글에서 익스텐션을 통한 보안이슈가 생기는 것을 막기 위해 다양한 방식으로 익스텐션의 자바스크립트가 실행되는 컨텍스트(context)들을 세분화 시켜두었기 때문에, 이러한 세분화된 컨텍스트에서 각각 어떤 작업이 수행가능한지, 어떻게 분리된 스크립트 컨텍스트간에 통신을 할 수 있는지를 이해하는 것이 생각보다 어렵다.
따라서 본 글에서는 이 부분의 개념에 대해서 자세히 비교 설명하고, 각각의 스크립트들이 잘 동작하도록 미리 설정된 보일러 플레이트 프로젝트를 소개할 예정이다. 대신 크롬 익스텐션에서 사용하는 세부적인 API들이나 manifest관련 세부 설정들은 구글에서 제공하는 문서에도 잘 나와있고, 다양한 sample project에도 케이스별로 잘 나와있기때문에 따로 정리하지는 않을 예정이다.
크롬 익스텐션과 그와 관련된 스크립트가 실행되는 컨텍스트에 따라서 page, background, content, injected script 4가지로 구분을 할 수 있다. 각각의 컨텍스트가 어떻게 다른지 좀 더 자세히 살펴보도록 하자.
chrome.* 모든 API에 접근 가능현재 사용자가 보고있는 페이지의 컨텍스트에서 실행되는 스크립트
chrome.runtime.* API에 접근 가능앞서 설명했듯이, 다른 컨텍스트의 script들과는 직접적인 접근이 막혀있기때문에 메시지 패싱 방식을 통해 통신을 해야한다. 케이스별로 조금씩 API가 다르지만 원리는 동일하다.
chrome.runtime.onMessagechrome.runtime.sendMessagewindow.postMessagevar port = chrome.runtime.connect(); port.postMessage익스텐션 내부의 파일을 로드할 경우 다음 API 를 통해서 액세스 가능
chrome.runtime.getURL(path)Content Scripts 를 로드하는 두가지 방식
chrome.tabs.executeScript({ code: '...'})chrome.tabs.executeScript({ file: 'file.js'})manifest파일의 'content_scripts' 섹션에 추가
"content_scripts": [
{
"matches": ["http://*.nytimes.com/*"],
"css": ["myStyles.css"],
"js": ["contentScript.js"]
}
],
디버깅 방법
page script의 컨텍스트에서 실행되는 코드가 필요하다면, content script의 컨텍스트로부터 script를 inject해야한다.
inject된 script의 경우 page script 컨텍스트에서 실행되므로 chrome.* 의 모든 API에대해서는 접근 불가
방법 1: 파일 로드
var s = document.createElement('script');
// TODO: add "script.js" to web_accessible_resources in manifest.json
s.src = chrome.extension.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
manifest에 web_accessible_resources 항목을 아래와 같이 추가하면, 익스텐션 패키지에 포함된 파일을 로드하는 것이 가능해진다. 구글 문서 참고
web_accessible_resources": ["script.js"]
CSP (Content Security Policy)가 설정된 웹사이트에서는 미리 설정된 domain에 대해서만 remote script를 load가능하기 때문에 함부로 아무 스크립트나 로드하는것이 불가능 하므로, inject할 스크립트는 되도록 익스텐션 패키지 안에 포함시키는 것이 안전하다.
방법2: 인라인(inline) 방식
var actualCode = '(' + function() {
// All code is executed in a local scope.
// For example, the following does NOT overwrite the global `alert` method
var alert = null;
// To overwrite a global variable, prefix `window`:
window.alert = null;
} + ')();';
var script = document.createElement('script');
script.textContent = actualCode;
(document.head||document.documentElement).appendChild(script);
script.remove();
앞서 살펴본 각각의 컨텍스트를 염두에 두고, 익스텐션을 개발할 때 기능에따라 어떤 컨텍스트에서 코드를 작성할지 정하고 시작하면 좀 덜 헷갈린다. 예를들어 탭관련 상태정보를 얻고싶다면 Chrome Application과 과련된 API를 사용해야 하기때문에 background script를 작성해야하고, 현재보고있는 페이지에 새로운 버튼을 추가하고 싶다면 content script를 작성해야하는 식이다.
Plain Javascript만으로도 크롬 익스텐션을 개발하는데 아무 문제가 없지만, React나 Vuejs 등의 프레임워크를 사용하면 UI가 있는 Chrome Extension을 만드는데 한결 생산성이 높아진다.
보일러플레이트 (boilerplate)
실제 적용 샘플
참고: Extension Reloader 익스텐션