크롬 확장 프로그램 만들기: 읽기 모드 실전 검증

최종 업데이트 2026.09.16

Manifest V3와 activeTab·scripting 최소 권한으로 읽기 모드 구조를 만들고, 방해 요소 숨김과 본문 19px 전환을 실제 캡처와 영상으로 검증했습니다.

크롬 확장 프로그램은 작은 기능 하나와 최소 권한으로 시작하는 편이 안전합니다. 읽기 모드 구조를 만들고 전환 동작을 로컬 화면에서 직접 검증했습니다.

첫 확장 프로그램의 범위를 한 문장으로 정한다

이번 프로젝트의 목표는 “사용자가 버튼을 누른 현재 탭에서만 주변 요소를 숨기고 본문 폭과 글자 크기를 읽기 좋게 바꾼다”입니다. 새 탭, 로그인, 서버 저장, 방문 기록 수집은 넣지 않았습니다. 기능을 좁히면 필요한 권한과 테스트 조건도 함께 줄어듭니다.

Chrome 공식 Hello World 안내는 확장 프로그램 폴더의 루트에 manifest.json을 두고, 툴바 아이콘을 눌렀을 때 열리는 팝업을 action으로 지정하는 기본 구조를 설명합니다. 개발 중에는 chrome://extensions에서 개발자 모드를 켜고 “압축해제된 확장 프로그램을 로드”해 폴더를 선택할 수 있습니다.

이 글의 프로젝트는 Manifest V3, 팝업 HTML, 팝업 스타일, 팝업 JavaScript 네 파일로 구성했습니다. 별도의 빌드 도구와 외부 라이브러리는 사용하지 않았습니다. 원격 서버에서 코드를 내려받아 실행하는 구조도 없습니다.

Manifest V3와 최소 권한 구성

Chrome 확장 프로그램 매니페스트 문서는 모든 확장 프로그램이 루트의 manifest.json에 구조와 동작 정보를 선언해야 한다고 설명합니다. 현재 지원되는 매니페스트 형식 값은 3입니다. 프로젝트에는 이름, 버전, 설명, 권한, 툴바 액션과 팝업 파일만 넣었습니다.

{
  "manifest_version": 3,
  "name": "Focus Reader Lab",
  "version": "1.0.0",
  "permissions": ["activeTab", "scripting"],
  "action": { "default_popup": "popup.html" }
}

host_permissions<all_urls>는 선언하지 않았습니다. 이 확장 프로그램은 모든 사이트에서 항상 동작할 필요가 없고, 사용자가 툴바에서 기능을 호출한 현재 탭에만 스타일을 적용하면 되기 때문입니다.

Chrome의 activeTab 안내에 따르면 이 권한은 사용자가 확장 기능을 호출했을 때 활성 탭에 임시 접근을 제공합니다. 사용자가 다른 출처로 이동하거나 탭을 닫으면 접근이 취소됩니다. 기능에 충분한지 확인한 뒤 더 넓은 호스트 권한을 요청해야 합니다.

팝업에서 현재 탭의 스타일 전환하기

팝업에는 “읽기 모드 전환” 버튼 하나를 두었습니다. 버튼을 누르면 현재 창의 활성 탭을 찾고 chrome.scripting.executeScript()로 짧은 함수를 주입합니다. 함수는 고정된 ID의 스타일 요소가 있으면 제거하고, 없으면 새 스타일을 추가합니다.

Chrome Scripting API 문서는 이 API를 사용하려면 매니페스트에 scripting 권한과 대상 페이지에 대한 호스트 권한 또는 activeTab 권한이 필요하다고 안내합니다. 실행 대상에는 현재 탭의 숫자 ID를 전달합니다.

주입하는 스타일은 본문 폭을 760px로 제한하고 배경과 본문 글자 크기를 바꾸며, aside, nav, 이름에 adbanner가 포함된 요소를 숨기는 학습용 규칙입니다. 실제 사이트마다 구조가 다르므로 이 선택자를 보편적인 읽기 모드 알고리즘으로 볼 수는 없습니다.

직접 실행한 읽기 모드 테스트

테스트 날짜: 2026년 9월 10일. 환경: Windows 11, Chrome 기반 브라우저, localhost 로컬 테스트 화면. 입력: 상단 탐색, 구독 배너, 본문, 사이드바, 관련 링크가 있는 샘플 문서. 기대 결과: 읽기 모드 뒤 방해 요소 3종 숨김, 본문 폭 760px, 큰 글자 선택 뒤 본문 19px, 버튼 상태 갱신.

확장 프로그램 폴더의 매니페스트가 버전 3인지, 권한 배열이 activeTabscripting 두 개뿐인지, 팝업 파일이 연결되는지를 먼저 검사했습니다. 실제 화면 동작은 같은 읽기 모드 로직을 버튼으로 조작하는 로컬 테스트 화면에서 확인했습니다. 이 화면은 Chrome 툴바 팝업을 촬영한 것이 아니라 기능 전환을 재현하고 관찰하기 위한 하니스입니다.

시작 상태에는 상단 사이트 탐색, 이메일 구독 배너, 오른쪽 인기 글·도구 상자, 아래 관련 글이 보였습니다. “읽기 모드 켜기”, “글자 크게”, “초기화” 버튼은 각각 한 개였고 읽기 모드 버튼의 aria-pressed 값은 false였습니다.

탐색 메뉴와 구독 배너와 사이드바가 보이는 읽기 모드 적용 전 실제 화면
읽기 모드 적용 전 본문 주변에 탐색, 구독 안내, 사이드바와 관련 글이 함께 표시된 실제 시작 화면입니다.

“읽기 모드 켜기”를 누르자 버튼 이름이 “읽기 모드 끄기”로 바뀌고 눌림 상태가 true가 됐습니다. 탐색·구독 배너·사이드바·관련 링크는 화면에서 접혔고 본문은 760px 한 열로 가운데 정렬됐습니다. 이어서 “글자 크게”를 누르자 본문 표시가 17px에서 19px로 바뀌었습니다.

읽기 모드와 큰 글자를 적용해 본문만 한 열로 보이는 실제 결과 화면
방해 요소를 숨기고 본문 폭 760px와 글자 19px를 적용한 뒤 ‘집중 읽기’ 상태가 표시된 결과입니다.
읽기 모드를 켜 주변 요소를 접고 큰 글자를 적용하는 약 3초 실제 작동 기록입니다.

같은 브라우저 세션에서 전환 전후를 약 0.18초 간격으로 기록했습니다. 마지막 상태에서 읽기 모드 클래스와 큰 글자 클래스가 모두 적용됐고, 방해 요소 3개 숨김, 본문 폭 760px, 본문 19px, “읽기 모드 + 큰 글자 적용” 안내를 확인했습니다.

접근 가능한 상태를 함께 표시한다

버튼 색상과 문구만 바꾸지 않고 읽기 모드 버튼에 aria-pressed를 사용했습니다. 끈 상태에서는 false, 켠 상태에서는 true로 갱신합니다. 보조 기술 사용자는 버튼이 현재 선택된 상태인지 확인할 수 있습니다.

버튼 이름도 “켜기”와 “끄기”로 바뀌어 다음 동작을 설명합니다. 별도 상태 영역에는 “원본 보기” 또는 “집중 읽기”를 표시합니다. 색상만으로 완료 상태를 구분하지 않고 방해 요소 수, 본문 폭, 글자 크기를 텍스트로 함께 제공합니다.

키보드로 탭 이동을 했을 때 포커스 테두리가 보이도록 :focus-visible 스타일을 넣었습니다. 모션 감소를 선호하는 사용자를 위해 prefers-reduced-motion 조건에서는 전환 시간을 거의 없앱니다. 읽기 모드는 접근성 기능 전체를 대신하지 않으며 원문 구조와 의미 있는 제목 순서는 그대로 중요합니다.

코드 작성 순서

먼저 빈 폴더에 매니페스트와 팝업 HTML을 만듭니다. 팝업 HTML은 인라인 스크립트 대신 외부 popup.js를 연결합니다. Manifest V3의 콘텐츠 보안 정책과 배포 규칙을 피하기 위한 우회가 아니라 구성 요소를 명확히 나누기 위한 기본 구조입니다.

다음으로 팝업 버튼을 찾고 클릭 이벤트를 연결합니다. chrome.tabs.query()로 활성 탭을 구한 뒤 탭 ID가 없으면 종료합니다. chrome.scripting.executeScript()에 탭 ID와 페이지 안에서 실행할 함수를 전달합니다.

주입 함수에는 외부 데이터와 사용자 입력을 넣지 않고 고정된 스타일만 사용했습니다. 스타일 요소 ID를 기준으로 추가와 제거를 반복해 같은 버튼으로 켜고 끌 수 있게 했습니다. 한 페이지에서 여러 번 눌러도 스타일 요소가 계속 쌓이지 않아야 합니다.

마지막으로 압축해제된 확장 프로그램을 로드하고 일반 HTTPS 페이지에서 테스트합니다. Chrome 내부 페이지, Chrome Web Store, PDF 뷰어, 다른 확장 페이지처럼 스크립트 주입이 제한되는 위치가 있으므로 오류를 사용자에게 설명해야 합니다. 매니페스트나 팝업 코드가 바뀌면 확장 프로그램을 다시 로드해야 하는 경우도 확인합니다.

실패 조건과 디버깅

첫 번째 실패 조건은 활성 탭을 얻지 못하는 경우입니다. 탭 ID가 없거나 접근이 제한되면 조용히 끝내기보다 팝업에 “이 페이지에서는 사용할 수 없음”을 보여 줄 수 있습니다. 콘솔 오류에는 URL 전체나 페이지 내용을 불필요하게 기록하지 않습니다.

두 번째는 사이트 구조가 달라 선택자가 맞지 않는 경우입니다. aside를 모두 숨기면 본문에 꼭 필요한 주석이나 목차도 사라질 수 있습니다. 사용자가 되돌릴 수 있어야 하고, 숨긴 요소 수가 예상보다 많으면 적용 전에 경고하는 방법을 고려합니다.

세 번째는 원래 사이트 스타일과 충돌하는 경우입니다. !important를 많이 사용하면 적용은 쉬워도 복구와 호환성이 나빠질 수 있습니다. 실제 제품에서는 대상 요소에 한정된 클래스, 저장된 원래 값, 페이지 이동 후 정리, 프레임별 동작을 시험해야 합니다.

네 번째는 같은 탭에서 여러 번 누르는 경우입니다. 스타일 ID가 하나만 존재하는지, 첫 클릭에 추가되고 둘째 클릭에 제거되는지 확인합니다. 페이지를 이동하면 activeTab 접근 범위가 어떻게 달라지는지도 공식 문서의 조건에 맞춰 검사합니다.

개인정보와 권한을 늘리기 전 질문

읽기 설정을 저장하려고 storage 권한을 추가하기 전에 정말 지속 저장이 필요한지 정합니다. 현재 탭에서 잠깐 쓰는 기능이라면 저장하지 않는 쪽이 단순합니다. 도메인별 설정을 기억해야 한다면 어떤 키를 얼마나 오래 보관하고 사용자가 지우는 방법을 제공할지 적습니다.

모든 사이트 권한을 요구하기 전에 사용자 동작으로 일시 권한을 얻을 수 있는지 확인합니다. 새 기능이 특정 사이트에서만 필요하면 선택적 호스트 권한이나 명시적 허용 목록을 검토합니다. 권한 설명은 기술 용어보다 사용자가 받는 기능과 읽는 데이터 범위를 분명하게 써야 합니다.

분석 도구를 붙일 때는 페이지 제목, URL, 본문이 외부로 전송되는지 별도로 확인합니다. 이번 프로젝트에는 네트워크 요청, 분석 코드, 저장소 권한이 없습니다. Chrome Web Store에 배포하려면 현재 프로그램 정책, 개인정보처리방침 필요 여부, 데이터 사용 공개 항목을 다시 검토해야 합니다.

이번 실습의 한계

캡처와 영상은 읽기 모드 핵심 상태를 재현한 로컬 테스트 화면입니다. Chrome 확장 프로그램 관리 화면에서 패키지를 설치하거나 실제 뉴스 사이트에 주입한 증거가 아닙니다. 실제 설치 검증에서는 압축해제 로드, 팝업 열기, 허용된 일반 페이지에서 전환, 제한 페이지 오류, 확장 재로드를 추가로 기록해야 합니다.

학습용 선택자는 문서 구조를 의미론적으로 분석하지 않습니다. 사이트별 헤더와 광고, 본문, 관련 콘텐츠를 완벽하게 구분할 수 없고 중요한 요소를 숨길 수 있습니다. DOM이 동적으로 바뀌는 페이지와 iframe, Shadow DOM, 인쇄 화면, 다국어 글꼴도 별도 대상입니다.

큰 글자가 모든 사용자에게 적합한 것도 아닙니다. 줄 길이, 행간, 대비, 확대, 키보드, 화면 읽기 프로그램을 함께 확인해야 합니다. 브라우저 자체의 읽기 모드나 확대 기능이 더 알맞은 경우도 있으므로 사용자 설정을 덮어쓰지 않아야 합니다.

결론: 기능보다 권한과 되돌리기를 먼저 설계한다

첫 크롬 확장 프로그램은 한 버튼으로 확인할 수 있는 작은 기능이 좋습니다. 현재 탭에만 임시 접근하고, 고정된 스타일을 추가·제거하며, 눌림 상태와 되돌리기 경로를 제공하면 기능과 권한을 함께 검증할 수 있습니다.

AI에게 구현을 요청할 때 목표·현재 상태·성공 조건·제약 조건·검증 순서를 적는 방법은 프롬프트로 타이머 웹앱을 만든 실전 기록에서 이어서 볼 수 있습니다. 이 사이트의 실험과 한계 표기 원칙은 소개 페이지에 정리했습니다.