원문 캡처 · github.com
원문 캡처 · github.com

Reeve는 여러 웹 앱을 하나의 WebView2 런타임에서 실행하고 앱마다 데이터와 권한을 분리하는 Windows용 PWA 실행기다. 제작자는 Rust로 만든 가벼운 실행기, 커널 수준의 메모리 제한, 권한을 명시적으로 부여하는 네이티브 기능 연결을 내세운다. 현재 macOS와 Linux용 대상은 구현되지 않았다.

Reeve 저장소

설계와 기능

Reeve가 해결하려는 문제는 Electron 앱마다 Chromium을 포함해 실행하는 데 따른 자원 사용이다. 저장소 설명에 따르면 Electron 앱 다섯 개는 각자 Chromium 프로세스 트리를 실행하지만, Reeve는 브라우저 프로세스 하나와 앱별 렌더러 다섯 개를 공유 런타임에서 실행한다. 각 앱에는 별도의 저장 프로필을 두고 전체 자원 사용량과 개별 프로세스에 Windows Job Object 메모리 제한을 적용한다.

실행기는 Chromium이나 Node 런타임을 묶지 않은 Rust 프로세스다. 앱별 WebView2 프로필은 쿠키, localStorage, IndexedDB, Service Worker 캐시를 분리한다. 따라서 한 앱에서 로그아웃해도 다른 앱의 로그인 상태는 영향을 받지 않으며, 실행기에서 앱 하나의 데이터만 지울 수 있다고 설명한다.

웹 앱은 파일 시스템 접근과 네이티브 알림 등 기능을 IPC 브리지를 통해 요청한다. 기본적으로 권한을 허용하지 않으며, 앱별 매니페스트에 명시된 권한을 확인한 뒤 요청 범위와 출처도 검사한다. 대시보드에서는 메모리 사용량을 확인하고 권한을 편집하거나 앱을 종료할 수 있다.

저장소가 설명하는 구조는 Rust 감독 프로세스에 SQLite 앱 카탈로그, 권한 저장소, Job Object 관리, 팝업 처리, PWA 매니페스트 처리, 네이티브 컨텍스트 메뉴가 포함되는 형태다. 실행기와 대시보드도 같은 WebView 환경을 사용한다. WebView2 브라우저 프로세스 안에서 앱별 프로필을 가진 렌더러를 실행하며, Windows Job Object가 프로세스별·전체 메모리 제한과 Job 종료 시 렌더러 정리를 맡는다.

각 렌더러는 ICoreWebView2Environment13::GetProcessInfos를 통해 Job Object에 연결하고 3초마다 다시 검색한다. 메모리 제한은 Windows 커널이 적용한다. WebView2가 자체 렌더러를 별도의 Job에 넣기 때문에 일부 AssignProcessToJobObject 호출은 ERROR_ACCESS_DENIED를 반환할 수 있으며, 저장소는 이를 예상된 상황으로 보고 처리한다고 설명한다. 브라우저 프로세스에 적용한 Job 전체 제한이 트리를 다룬다. 앱은 with_profile_name(app.id)로 생성해 공유 브라우저 프로세스 안에서도 앱별 데이터를 분리한다.

구현 상태와 설치

저장소는 다음 기능이 동작한다고 명시한다.

  • 실행기 UI: 어두운 테마, URL을 이용한 앱 설치, 실시간 검색·필터
  • 네이티브 컨텍스트 메뉴: 앱을 우클릭해 Open, Clear Data, Uninstall 실행. 전용 스레드에서 처리해 이벤트 루프를 막지 않는다.
  • 앱별 WebView2 프로필과 쿠키, localStorage, IndexedDB, 캐시 분리
  • 프로필 이름 검증과 기록. 이름이 유효하지 않으면 경고를 남기고 공유 프로필로 대체한다.
  • 앱 관리: 제거할 때 DB 레코드와 외래 키로 연결된 항목을 삭제하고, 데이터 지우기는 앱의 로컬 저장소를 삭제한 뒤 다시 불러온다.
  • 개발자 SDK: 각 앱에 Promise 기반 window.reeve API를 주입한다. 예시는 reeve.fs.read, reeve.notify 등이다.
  • 보안: IPC와 팝업에 Public Suffix List(PSL) 기반 출처 매칭을 적용하고 브리지 호출에 앱별 500ms 제한을 둔다.
  • 아이콘 처리: manifest.json에서 앱 이름을 가져오고 아이콘을 Base64로 인코딩해 WebView2의 file:// 제한을 피한다. 네이티브 창에도 아이콘을 적용한다.
  • 앱 전체가 WebView2 브라우저 프로세스 하나를 공유하는 런타임
  • 프로세스별 및 Job 전체 메모리를 제한하는 Job Object 관리 기능
  • fs.pick, fs.read, fs.write, notify 기능을 제공하는 권한 브리지
  • OAuth 및 같은 브랜드의 팝업은 관리되는 창으로 열고 외부 링크는 시스템 브라우저로 전달하는 팝업 처리
  • 실시간 메모리, 권한 편집, 앱 종료 스위치, 최고 메모리 추적을 제공하는 대시보드
  • 반복 가능한 콜드 스타트 및 안정 상태 메모리 측정 도구

아직 구현되지 않은 항목은 macOS와 Linux 지원, 앱별 프로필 분리를 적용한 콜드 스타트 벤치마크, 테마에 맞춘 owner-draw 컨텍스트 메뉴다. 기존 콜드 스타트 수치는 앱별 프로필 분리보다 먼저 측정됐으며, 프로필별 초기화 비용을 다시 측정하는 작업은 향후 계획에 있다.

요구 사항은 Windows 10 또는 11, 안정 버전 Rust 1.75 이상, C++ 워크로드가 포함된 Visual Studio Build Tools(MSVC 링커), GNU Make다. WebView2 Runtime은 최신 Windows 빌드에 사전 설치돼 있다고 안내한다. Make는 Git for Windows와 MSYS2에 포함되며, 없으면 choco install make 또는 scoop install make로 설치할 수 있다.

WebView2가 설치됐는지 확인하는 명령은 다음과 같다.

powershell
Get-ItemProperty -Path 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F1E7E9DA-3F7D-4A15-A8D0-1C3A9F0F6C6C}' -Name pv -ErrorAction SilentlyContinue

결과가 없으면 Evergreen WebView2 Runtime을 설치한다.

저장소는 reeve 실행기와 benchmark 측정 도구, 두 바이너리로 구성된 워크스페이스다. Git으로 내려받아 실행하는 명령은 다음과 같다.

sh
git clone https://github.com/MarcelNazare/Reeve.git
cd Reeve
cargo check
cargo run # launches the launcher
cargo run --bin benchmark -- --help # launches the benchmark harness

포함된 Makefile을 이용할 수도 있다.

make
make # build
make run # build + run launcher with debug logging
make release # optimized build (LTO, stripped)

Windows에서는 cmd.exe가 아니라 Git Bash 또는 MSYS2에서 make를 실행해야 한다. run 대상은 POSIX 셸 문법으로 RUST_LOG를 설정한다.

데이터 디렉터리는 %APPDATA%\\Reeve\\Reeve\\data\\ 아래에 있다. catalog.db에는 앱, 권한, 세션이 저장되며 icons\\에는 PWA 아이콘 캐시가, webview\\에는 공유 WebView2 사용자 데이터 폴더가 놓인다. webview\\Default\\는 공유 대체 프로필이고 webview\\<app-id>\\는 앱별 프로필이다. 각 앱의 localStorage, IndexedDB, 쿠키, Service Worker 캐시는 앱별 폴더에 저장된다. Clear Data는 해당 앱의 디렉터리만 지운다.

앱 추가와 개발자 API

실행기 위쪽 검색창에 URL을 붙여 넣고 Add를 누르면 앱을 등록한다. 이후 백그라운드 스레드가 페이지 HTML에서 <link rel="manifest">를 찾고 manifest.json을 분석해 앱 이름을 가져온다. 가장 해상도가 높은 아이콘을 내려받아 data/icons/에 저장하고 작업이 끝나면 그리드를 갱신한다. 매니페스트가 없으면 Google 파비콘을 대신 사용한다. src/main.rs에서 코드로 앱을 미리 등록할 수도 있으며, 같은 앱을 중복 생성하지 않도록 멱등성을 갖췄다고 설명한다.

Reeve는 실행하는 앱에 사전 스크립트를 주입해 window 객체에 Promise 기반 SDK를 제공한다. 저장소의 사용 예시는 다음과 같다.

javascript
// Read a file (requires fs.read permission)
try {
 const { content } = await window.reeve.fs.read("C:\\Users\\Public\\notes.txt");
 console.log(content);
} catch (err) {
 console.error("Access denied:", err.message);
}

// Send a native notification (requires notify permission)
await window.reeve.notify("Build Complete", "Deployment finished in 3.2s.");

// Open a native file picker (requires fs.pick permission)
const { paths } = await window.reeve.fs.pick();

브리지 호출은 세 단계를 통과해야 한다. IPC 페이지의 호스트가 앱 실행 출처와 일치하는지 PSL의 eTLD+1 매칭으로 확인하고, 데이터베이스에 앱의 명시적 권한이 있는지 검사한 다음, 정규화한 요청 경로가 정규화한 허용 범위 안에 있는지 확인한다. 앱 하나가 네이티브 대화상자를 반복 호출하지 못하도록 브리지는 앱별로 500ms에 한 번의 호출만 허용한다.

대시보드는 실행기 상단에서 Dashboard를 눌러 연다. 별도 창이지만 같은 WebView2 환경을 공유한다. 전체 프로세스 트리의 작업 집합 합계, Windows Job Object가 관찰한 최고 커밋 메모리, 앱의 start_url과 렌더러 프레임을 대조해 추정한 앱별 메모리를 표시한다. 앱별로 notify, fs.pick, fs.read, fs.write 권한을 부여하거나 취소할 수 있다. 종료 스위치는 실행 중인 앱의 렌더러를 즉시 종료하고 SQLite 세션을 끝낸다.

그리드에서 앱을 우클릭하면 네이티브 Win32 메뉴가 뜬다. Open은 앱을 실행하며 이미 실행 중이면 창에 포커스를 준다. Clear Data는 앱의 로컬 저장소, 세션 저장소, IndexedDB, 캐시를 지우고 다시 불러온다. 이 기능은 앱이 실행 중일 때만 동작한다. 데이터를 지우는 스크립트를 실행 중인 WebView에 주입하기 때문이다. Uninstall은 카탈로그에서 앱을 제거하고 연결된 권한과 세션 이력도 삭제한다. 메뉴는 전용 스레드에서 처리하므로 메뉴가 열린 동안에도 이벤트 루프가 메모리를 확인하고 대시보드를 갱신하며 포커스를 추적한다고 설명한다.

벤치마크와 제약

src/bin/benchmark.rs의 측정 도구는 설정별로 격리해 실행한다. 초기 측정에서는 Reeve의 메모리 안정화 시간인 t_stable이 41.1초, Chrome 탭은 9.2초였다. WebView2 초기화 과정을 분리해 살핀 결과, 제작자는 병목이 GPU 프로세스와 DirectComposition 초기화에 있다고 설명한다. WebView2가 그래픽 드라이버와 협상하고 최초 DirectComposition 표면을 만드는 동안 호스트 애플리케이션을 동기적으로 막는다는 설명이다.

소프트웨어 렌더링(WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS="--disable-gpu")을 사용하면 Reeve의 메모리 안정화 시간은 41.1초에서 3.3초로 줄었다고 저장소는 기록한다. 다만 소프트웨어 렌더링에서는 Figma와 Google Maps처럼 캔버스를 많이 쓰는 앱을 사용할 수 없게 된다. Reeve는 하드웨어 가속을 유지하면서 Windows의 창 스로틀링을 줄이기 위해 --disable-features=CalculateNativeWinOcclusion을 사용한다. 이 수치는 앱별 프로필 분리보다 앞서 측정됐다. 저장소는 분할 쿠키를 켜면 첫 실행 때 프로필마다 초기화 비용이 조금 더 들 것으로 예상하며, 재측정을 계획하고 있다.

저장소가 공개한 사전 격리 벤치마크는 Linear, Notion, Figma, Slack, Spotify를 대상으로 했다. 아래 수치는 원문에 제시된 측정값이다.

시나리오t_stable(대리 지표)t_ready(정확한 값)
reeve41100 ms12460 ms
chrome-tabs9200 ms—
시나리오최고 메모리안정 상태 메모리최대 프로세스 수
reeve2575.5 MB2165.5 MB18
chrome-tabs2212.2 MB2195.3 MB24

저장소는 이 다섯 개의 무거운 앱을 실행한 조건에서 Reeve의 안정 상태 메모리 2165MB와 Chrome의 2195MB가 사실상 비슷하다고 해석한다. 저장소가 말하는 “80% 적은 RAM” 주장은 Electron과 비교할 때이며 Chrome 탭과 비교한 결과는 아니다. Reeve는 Chrome의 24개보다 적은 18개 프로세스로 안정 상태 메모리가 비슷했다. 이 하드웨어에서 WebView2의 동기식 GPU 설정은 메모리 안정화 시간에 약 30초를 더하는 콜드 스타트 비용이라는 설명도 덧붙였다.

보안과 알려진 제한

저장소는 네이티브 브리지를 공격 표면으로 보고 기본 거부, 출처 확인, 호출 제한, 경로 범위 확인을 설계 원칙으로 든다. Public Suffix List의 psl 크레이트로 eTLD+1 도메인을 비교해 evil.co.uk가 co.uk의 앱과 일치하는 일을 막는다. 경로의 양쪽을 정규화하고 심볼릭 링크와 ..를 거부한다. fs.pick은 운영체제 대화상자를 사용해 사용자가 파일을 선택하게 한다. 메모리 제한은 Job Object가 커널 수준에서 적용하며 앱마다 WebView2 프로필을 분리해 다른 앱의 쿠키와 로컬 데이터를 읽을 수 없게 한다고 설명한다.

다만 Clear Data는 앱이 닫혀 있으면 즉시 실행되지 않는다. 실행 중인 앱의 WebView에 삭제 스크립트를 넣기 때문에 닫힌 앱은 다음 실행 때까지 삭제가 미뤄진다. 종료 스위치는 보안 경계가 아니라 사용 편의를 위한 기능이며, 실제 격리는 Job Object 제한이 맡는다. 프로필 이름이 거부되면 경고 로그를 남기고도 조용히 공유 프로필로 대체할 수 있다. 격리가 적용되지 않은 것 같으면 콘솔에서 profile name rejected를 확인하라고 안내한다.

로드맵과 문제 해결

로드맵에서 완료로 표시한 항목은 공유 브라우저 프로세스, Job Object 메모리 제한과 렌더러 연결, 첫 버전 권한 브리지, PSL 출처 검사와 호출 제한, Promise 기반 SDK, 앱 제거·데이터 삭제, 앱별 프로필, 스레드 안전성을 고려한 Win32 메뉴, 프로필 이름 검사와 대체 기록이다. 미완료 항목은 앱별 프로필을 적용한 콜드 스타트 측정, owner-draw 메뉴, macOS 지원(WKWebView와 소프트 메모리 제한), Linux 지원(WebKitGTK와 cgroups v2)이다.

저장소의 문제 해결 안내는 다음과 같다.

  • with_profile_name을 찾지 못하면 wry = "0.56"으로 올린다. 해당 기능은 0.55가 아니라 0.56.0에 들어갔다.
  • HWND 타입이 맞지 않으면 windows 0.61에서 HWND(isize)가 아니라 HWND(*mut c_void)가 필요하다. hwnd as *mut core::ffi::c_void로 변환한다.
  • TPM_RETURNCMD | TPM_NONOTIFY 타입 오류는 windows 0.61의 플래그 타입이 새 타입이므로 .0으로 내부 u32를 꺼낸다.
  • cargo run이 실행할 바이너리를 정하지 못하면 Cargo.toml의 [package] 아래에 default-run = "reeve"를 두거나 cargo run --bin reeve를 사용한다.
  • 한 앱에서 로그아웃했는데 다른 앱에도 영향을 주면 콘솔의 profile name rejected를 확인한다. 앱이 공유 프로필로 대체됐을 가능성이 있다.
  • FOREIGN KEY constraint failed가 나오면 최신 catalog.rs가 시작 시 삭제를 연쇄 적용하므로 한 번 실행해 DB를 복구한다.
  • 아이콘이 비어 있으면 아이콘을 Base64 데이터 URL로 읽는 v0.6.0 이상인지 확인한다.
  • 컨텍스트 메뉴가 UI를 멈추면 std::thread::spawn으로 별도 스레드에서 실행되는지 확인한다.
  • couldn't read migrations/001_init.sql은 프로젝트 루트에 migrations/001_init.sql이 있는지 확인한다.
  • couldn't read assets/reeve.ico는 build.rs가 요구하는 assets/reeve.ico가 있는지 확인한다.
  • 대시보드 메모리가 0 B로 나오면 먼저 그리드에서 앱을 실행한다.

Reeve는 초기 개발 단계다. 저장소는 macOS와 Linux 이식, 어두운 테마에 맞는 Win32 컨텍스트 메뉴, 앱별 프로필의 콜드 스타트 비용 측정, 앱이 닫혀 있을 때 디스크의 프로필 폴더를 지우는 기능을 기여 과제로 제안한다. 비중 있는 작업을 시작하기 전에는 이슈를 열어 달라고 안내한다. 라이선스는 MIT다. LICENSE

저장소가 밝힌 주요 의존 프로젝트는 WebView와 창 관리를 위한 wry 및 tao, Win32 API 바인딩을 위한 windows, WebView2 COM 바인딩을 위한 webview2-com, 출처 검사를 위한 psl, 매니페스트 처리를 위한 reqwest와 scraper, 아이콘 디코딩을 위한 image, 창에서 HWND를 가져오는 raw-window-handle이다. 저장소는 공유 런타임 구상이 최신 데스크톱 앱은 브라우저를 묶은 웹 앱이라는 관찰에서 영감을 받았다고 설명한다.