3.2. 크롬 익스텐션 만들어보기 Part II - Chrome API 사용하기
이전 포스팅, '3.1.크롬 익스텐션 - 서비스 워커 심화'에서 서비스 워커가 크롬 익스텐션의 다른 구성 요소인 content 스크립트 그리고 웹 브라우저의 주소창(=omnibox)과 어떤 관계에 있고 어떻게 상호작용 하는지 다루어보았다(아래 그림 참조).
이번 포스팅에서는 익스텐션의 UI, 그리고 크롬 웹 브라우저가 제공하는 API에 집중해보려 한다. 서비스 워커, content 스크립트 없이 익스텐션의 UI와 크롬 API를 가지고 크롬 브라우저를 컨트롤 할 수 있다. 아래 그림은 이번 포스팅에서 다룰 익스텐션의 구조를 보여준다. popup.js 그리고 웹 브라우저의 상호작용을 눈여겨 보기바란다.
익스텐션을 작성하기 위해 가장 먼저 해야할 일은 익스텐션이 제공할 기능을 정의하고, 이 기능들을 제공하기 위해 필요한 익스텐션 구조를 설계하는 일이다. 따라서 작성해 볼 익스텐션의 기능에 대해 소개하면서 시작하려한다. 아래 그림과 같이 그 성격이 같은 페이지들(여기서는 구글 크롬 개발 문서에 속한 페이지들)을 계속 열다 보면 탭들이 늘어난다.
이번 예제 익스텐션에서는 이렇게 서로 성격이 같은 탭들을 그룹으로 만들어 관리하게 해 줄것이다.
이같은 기능을 제공하기 위해서는 탭을 관리하기 위한(그룹 지정하기, 그룹 해제하기) 사용자 인터페이스(UI) 그리고 사용자 인터페이스와 크롬 웹 브라우저의 탭 관리 기능의 상호작용으로 충분하다. 서비스 워커를 이용해 외부 서버에서 무언가를 가져오거나 content 스크립트를 이용해 특정 웹 페이지와 상호작용할 필요가 없다.
같은 예제를 구글 공식 문서(https://developer.chrome.com/docs/extensions/get-started/tutorial/popup-tabs-manager)를 통해서도 살펴볼 수 있다. 다만 이 포스팅과 설명하는 방식만 다를 뿐이다.
필요한 요소들의 윤곽이 잡혔고 각 요소들의 역할도 정리해 볼 수 있다. 익스텐션 파일들을 저장할 폴더를 tabs_manager라는 이름으로 만든 후 이 폴더에 파일들을 작성한다.
익스텐션 요소 | 역할 |
익스텐션 UI - popup.html, popup.js | · 익스텐션 자체의 UI 제공 · 사용자 입력에 따라 브라우저와 상호작용 |
manifest.json | 익스텐션의 구조 서술 |
먼저 익스텐션 구조를 기술하는 필수 파일인 manifest.json 파일을 작성해보자. 앞서 포스팅들에서 살펴보았듯이 manifest.json 파일 안에 모든 익스텐션이 공통적으로 반드시 포함해야 하는 요소들이 있고, 익스텐션이 제공하는 기능에 따라 필요한 요소들이 있다. 이번 예제를 기준으로 정리해보면 아래와 같다.
항목 | 역할 | 필수 여부 |
manifest_version | manifest 파일의 버전 | 필수 |
name | 익스텐션의 이름 | 필수 |
version | 익스텐션의 버전 | 필수 |
icons | 익스텐션 아이콘 | 선택 |
action | 익스텐션 아이콘을 클릭했을 때 실행될 코드(스크립트) | 선택 |
host_permissions | 익스텐션이 적용될 페이지들 | 선택. 특정 페이지들을 대상으로 할 경우 필요. 생략할 경우 모든 페이지들을 대상으로 익스텐션이 실행된다. |
permissions | 익스텐션 실행에 필요한 브라우저 API 사용 권한 | 선택 브라우저 API를 사용하는 경우에만 필요. |
manifest_version ~ version 항목들은 모든 익스텐션에 공통적으로 필요한 항목이고, 작성할 익스텐션은 구글 크롬 익스텐션 개발문서 페이지에서만 동작할 것이므로 host_permissions 항목을 https://developer.chrome.com/*로 지정할 것이다. 그리고 크롬의 브라우저 탭 관리를 위해 크롬에서 제공하는 API를 사용할 것이므로 API 사용 권한인 permissions 항목에 tabGroups을 지정할 필요가 있다.아래와 같이 manifest.json 파일을 작성하자.
{
"manifest_version": 3
, "name": "Tab Manager for Chrome Dev Docs"
, "version": "1.0"
, "icons": {
"16": "images/icon-16.png"
, "32": "images/icon-32.png"
, "48": "images/icon-48.png"
, "128": "images/icon-128.png"
}
, "action": {
"default_popup": "popup.html"
}
, "host_permissions": [
"https://developer.chrome.com/*"
]
, "permissions": [
"tabGroups"
]
}
<tabs_manager/manifest.json>
익스텐션 아이콘들을 https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/functional-samples/tutorial.tabs-manager/images위치에서 다운로드 받은 후 프로젝트 폴더인 tabs_manager 안에 images라는 폴더를 만들고 tabs_manager/images 폴더 안에 아이콘 파일들을 저장한다.
이제 익스텐션의 UI 화면을 구성하자. 익스텐션은 크롬 익스텐션 개발 문서들 탭을 하나로 묶어주거나 풀어준다(구글에서 제공하는 예제에서는 풀어주는 기능은 없지만 추가하였다). 그리고 어떤 탭들이 그룹핑되어 있는지 익스텐션 UI 화면을 통해 보여준다. 즉, 버튼 2개와 그룹핑된 페이지들 표시로 구성된 익스텐션 UI면 충분하다(아래 그림 참조).
위와 같은 구성을 프로젝트 폴더인 tabs_manager 폴더 안에 popup.html 파일로 아래와 같이 HTML로 작성하자. 여기서는 디자인적인 내용들을 추가해 주기 위해 CSS(Cascade Style Sheet) 파일을 사용하였다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="stylesheet" href="./popup.css"/>
</head>
<body>
<template id="li_template">
<li>
<a>
<h3 class="title">Tab Title</h3>
<p class="pathname">Tab Pathname</p>
</a>
</li>
</template>
<h1>Google Dev Docs</h1>
<button id="group_btn">Group Tabs</button>
<button id="ungroup_btn">Ungroup Tabs</button>
<ul></ul>
<script src="./popup.js" type="module"></script>
</body>
</html>
<tabs_manager/popup.html>
popup.html 파일의 body 섹션의 template 요소는 탭 그룹핑 대상들의 정보를 표시할 템플릿을 지정하고 있다. 탭의 제목(title)과 페이지의 구글 크롬 익스텐션 개발문서 페이지 안에서의 세부 경로(pathname)가 표시될 것이다. 이 template 요소는 익스텐션 UI를 실제 동작시키는 동적인 요소인 자바스크립트 파일(조금 이따 popup.js라는 이름으로 작성)에서 활용할 것이다. template 요소 아래쪽에 보면 그룹핑(Group Tabs)/그룹해제(Ungroup Tabs) 버튼이 위치하고 있다.
버튼 아래 위치하는 <ul></ul> 요소는 내용이 비어있는데, 대상 탭에 대한 정보들이 요약된 tamplate들이 이 <ul></ul> 요소들 안에 위치하게 될 것이다.
popup.html 파일의 마지막에는 이 HTML 파일을 컨트롤 할 자바스크립트 파일인 popup.js 파일을 스크립트로 포함하도록 지정한다.
자바스크립트 파일을 작성하기 전에 popup.html 파일의 디자인적인 요소들을 popup.css 파일 안에 아래와 같이 작성하자.
body {
width: 20rem;
}
ul {
list-style-type: none;
padding-inline-start: 0;
margin: 1rem 0;
}
li {
padding: 0.25rem;
}
li:nth-child(odd) {
background: #80808030;
}
li:nth-child(even) {
background: #ffffff;
}
h3, p {
margin: 0;
}
<tabs_manager/popup.css>
CSS 파일의 내용을 살펴보면 익스텐션의 UI인 popup.html 페이지의 각 요소들의 가로 크기(width), 간격(margin), 여백(padding), 배경색(background) 등을 지정하고 있음을 알 수 있다.
이제 popup.html 페이지의 버튼을 누르면 브라우저 탭을 관리하고 그룹핑 대상 탭들의 정보를 표시해주는 기능을 popup.js 파일 안에 작성해보자.
const tabs = await chrome.tabs.query({
url: [
"https://developer.chrome.com/docs/webstore/*"
, "https://developer.chrome.com/docs/extensions/*"
]
}); // 지정한 url 값을 갖는 탭들을 얻는다.
const collator = new Intl.Collator(); // Collator는 페이지들을 정렬하는(collate)데 쓰이는 클래스다.
tabs.sort((a, b) => collator.compare(a.title, b.title)); // 탭들을 title 기준으로 정렬한다.
const template = document.getElementById("li_template"); // popup.html 에서 "li_template" 요소를 얻는다.
const elements = new Set(); // Set 클래스는 유일한 값들을 저장하는 클래스다. 본문에서 좀 더 살펴보겠다.
for (const tab of tabs) {
const element = template.content.firstElementChild.cloneNode(true); // <li></li>
const title = tab.title.split("-")[0].trim(); // 탭 제목에서 -기호 앞 부분을 얻고 공백을 제거(trim)한다.
const pathname = new URL(tab.url).pathname.slice("/docs".length); // tab URL에서 "/docs" 이후 부분을 추출한다.
// 탭 정보를 가지는 리스트<li></li> 요소를 만든다.
element.querySelector(".title").textContent = title;
element.querySelector(".pathname").textContent = pathname;
element.querySelector("a").addEventListener("click", async () => {
// 선택된 탭(tab)을 활성 상태로 만든다.
await chrome.tabs.update(tab.id, {active: true});
// 선택된 탭을 포함하는 윈도우를 활성화(fucused)시킨다.
await chrome.windows.update(tab.windowId, {focused: true});
});
elements.add(element); // <li>...</li><li>...</li>...
}
// 탭 정보가 저장된 리스트 요소들을 popup.html의 <ul></ul> 요소 안에 삽입한다.
document.querySelector("ul").append(...elements); // <ul><li>...</li><li>...</li>...</ul>
const buttonGrp = document.querySelector("#group_btn"); // 그룹 버튼
buttonGrp.addEventListener("click", async () => {
// 구조분해할당. tab 정보가 들어있는 각 객체(딕셔너리)에서 id 키(key)의 값(value)을 얻는다. [id1, id2, ...]
const tabIds = tabs.map(({id}) => id); // 대상 탭들의 ID를 얻는다.
const group = await chrome.tabs.group({tabIds}); // tab ID를 이용해 탭들의 그룹 생성
await chrome.tabGroups.update(group, {title: "DOCS"}); // 탭 그룹의 제목을 "DOCS"로 수정
});
const buttonUngrp = document.querySelector("#ungroup_btn"); // 언그룹 버튼
buttonUngrp.addEventListener("click", async () => {
const tabIds = tabs.map(({id}) => id); // 그룹핑 대상 탭들의 ID를 얻는다.
await chrome.tabs.ungroup(tabIds); // 그룹핑 대상 탭들을 ungroup한다.
});
< tabs_manager/popup.js>
익스텐션의 UI인 popup.html의 group, ungroup 버튼을 클릭할 때 마다 수행되어야 할 동작이 popup.js에 기술되어 있다. 우선 전체적인 흐름을 살펴보면 아래와 같다.
① 지정한 URL이 표시된 탭 정보 수집한다.
↓
② ①에서 수집된 탭들의 페이지 제목, 문서 경로 등의 정보를 리스트 요소(<li></li>)로 가공한다.
↓
③ 그룹 버튼 이벤트 핸들러: 수집된 탭들을 하나의 그룹으로 묶는다.
④ 언그룹 버튼 이벤트 핸들러: 수집된 탭들을 그룹 상태에서 해제한다.
탭 관리를 위해 사용한 크롬 탭 API들을 정리하면 아래와 같다.
API | 기능 |
chrome.tabs.query(queryInfo: object, callback?: function) | queryInfo 인자로 전달한 속성을 갖는 모든 탭들을 반환한다. |
chrome.tabs.update(tabId?: number, updateProperties: object, callback?: function) | tabId 인자로 전달한 ID 값을 갖는 탭을 updateProperties 속성의 값으로 수정한다. |
chrome.windows.update(windowId: number, updateInfo: object, callback?: function) | windowId에 전달된 ID를 갖는 window의 속성들을 updateInfo로 전달받은 속성들로 수정한다. |
chrome.tabGroups.update(groupId: number, updateProperties: object, callback?: function) | groupId에 전달된 ID를 갖는 탭 그룹의 내용을 updateProperties 인자의 내용으로 수정한다. |
chrome.tabs.group(options: object, callback?: function) | 전달받은 탭 ID에 해당하는 탭들을 그룹으로 묶는다. |
chrome.tabs.ungroup(tabIds: number|[number,...number[]], callback?: function) |
탭 ID의 탭들(Array(list) 형태로 전달)을 그룹에서 해제한다. |
popup.js에서 그룹핑 대상 탭들의 정보가 표시된 <li> 요소들을 보관하기 위해 Set 클래스를 사용하였다(const elements = new Set();). Set 클래스는 유일한 값들을 저장할 수 있는 객체를 제공한다. 저장되는 값들은 같은 값을 가지지 않는(unique), 유일한 원소들로 이루어져 있어야 한다. 예를 들면 아래와 같이 사용한다.
const a = new Set([1, 2, 3]);
const b = new Set([{1: "one"}, {2: "two"}, {3: "three"}]);
Set 클래스에서 각 원소들을 꺼낼 때는 구조분해할당(destructuring assignment. 리스트, 딕셔너리등을 그 원소들로 분해하는 것)연산으로 분해한 후 원소들을 꺼낸다.
console.log(...a)
console.log([...b][0]) // {1: "one"}
popup.js 스크립트를 마지막으로 익스텐션에 필요한 manifest 파일과 익스텐션 UI 파일들이 모두 준비되었다. 아래와 같이 tabs_manager 폴더(디렉토리) 안에 파일들이 있을것이다.
이제 작성이 완료된 tabs manager 익스텐션을 크롬 브라우저에 추가하고(상세한 추가 방법은 '2.1 크롬 익스텐션 만들어 보기 Part I - 기본 구조' 포스팅 참고) 익스텐션의 아이콘을 클릭해 보자. 탭 그룹으로 관리할 대상 페이지들이 없는 상태에서는 아래와 같이 버튼만 두 개 보일것이다.
이제 크롬 익스텐션 개발 문서들을 몇 개 열어보자. 예를 들어 https://developer.chrome.com/docs/extensions/reference/api 아래에 있는 API 참고 문서들을 몇 개 열어보면 아래와 같이 탭 그룹으로 관리 가능한 탭들이 익스텐션의 GUI에 표시될 것이다.
익스텐션 UI에서 Group Tabs 버튼을 클릭하면 아래와 같이 그룹핑 대상 탭들이 그룹으로 지정될 것이다.
이 상태에서 익스텐션 UI의 Ungroup Tabs 버튼을 클릭하면 그룹이 해제된다.
이번 포스팅에서는 크롬에서 제공하는 API를 사용하는 방법에 대해 살펴보았다. 크롬 익스텐션은 특정 웹 페이지와 상호작용하여 페이지에 기능을 더하는 것 외에도 웹 브라우저 자체를 변화시킬 수 있고 이는 크롬에서 제공하는 API를 이용해서 가능하다. 크롬 API 문서 https://developer.chrome.com/docs/extensions/reference/api 페이지를 보면 다양한 기능을 제공하는 API들을 살펴볼 수 있다.
■