네이티브 플러그인
페르소나가 지정하는 공유 라이브러리로 어떤 WebAPI 호출이든 네이티브 속도로 바꿉니다.
플러그인은 헤더 하나, bauta_plugin.h에 맞춰 독립적으로 빌드한 공유 라이브러리입니다. Linux에서는 .so,
macOS에서는 .dylib, Windows에서는 .dll입니다. 페르소나가 플러그인을 지정하면, bauta는 모든 WebAPI 호출의
출구, 즉 페르소나 규칙이 적용되는 바로 그 지점에서 플러그인을 호출합니다.
플러그인은 답을 계산하거나, 호출의 인수로 답을 고르거나, 반환된 버퍼를 고쳐 쓸 수 있습니다. 모두 C로, 네이티브
속도로, 브라우저를 다시 빌드하지 않고 합니다. 페르소나 규칙이 먼저 실행되며, 플러그인은 그 결과를 보고 덮어쓸 수
있습니다. 헤더와 예제는 bauta-browser 리포지토리의 plugins/
디렉터리에 있습니다.
내보내는 두 심벌
플러그인은 심벌 두 개를 내보냅니다.
uint32_t bauta_plugin_init(const BautaHost* host);
void bauta_plugin_transform(BautaCall* call);bauta_plugin_init은 로드할 때 한 번 호출됩니다.host를 보관하세요. 로드를 받아들이려면 빌드할 때 쓴BAUTA_PLUGIN_ABI를 반환하고, 거절하려면 0을 반환합니다.bauta_plugin_transform은 모든 호출의 출구에서 호출됩니다.host->label(call)을 확인하고, 처리하지 않는 호출에서는 아무것도 설정하지 않고 반환합니다.
버전
bauta는 자기 버전 이하의 어떤 ABI 버전으로 빌드된 플러그인이든 불러옵니다. 플러그인은 host->abi가 자신이 빌드된
버전 이상인 호스트를 받아들여야 하며, 어떤 필드는 host->abi가 그 필드를 추가한 버전에 이를 때만 써야 합니다.
BautaHost가 제공하는 것
| 함수 | 용도 |
|---|---|
label(call) | 페르소나 규칙이 부르는 이름 그대로의 호출 레이블("Navigator.get platform"). |
argc(call), arg_is_number(call, i), arg_number(call, i) | 호출의 인수. |
set_number, set_bool, set_string | 반환값을 바꿉니다. |
set_json(call, json) | 반환값을 임의의 JSON 값으로 바꾸며, 호출이 Promise를 반환하면 resolve된 Promise로 감쌉니다. |
return_bytes(call, &ptr, &len) | 반환된 RGBA 바이트(getImageData의 픽셀). 그 자리에서 수정합니다. |
return_floats(call, &ptr, &count) | 반환된 Float32Array(getChannelData). 그 자리에서 수정합니다. |
arg_is_string(call, i), arg_string(call, i, buf, cap) | ABI 2: UTF-8로 된 문자열 인수. |
persona(call), seed(call, domain) | ABI 2: 페르소나의 이름과, 도메인별 노이즈 시드(설치마다 키가 다름). |
realm(call) | ABI 2: 호출한 주체. BAUTA_REALM_PAGE, BAUTA_REALM_DEDICATED_WORKER, BAUTA_REALM_SHARED_WORKER, BAUTA_REALM_SERVICE_WORKER, BAUTA_REALM_OTHER 중 하나. |
set_json은 페르소나 규칙과 같은 방식으로 답합니다. 속성의 배열이나 객체는 읽을 때마다 같은 객체이며(배열은
동결), Promise를 반환하는 호출은 resolve된 Promise를 받습니다.
빌드
헤더가 두 진입점을 내보내므로(BAUTA_PLUGIN_EXPORT) 링커 옵션은 필요 없습니다. plugins/examples에서 빌드합니다.
gcc -shared -fPIC -O2 -I.. sample_plugin.c -o sample.so # Linux
clang -shared -O2 -I.. sample_plugin.c -o sample.dylib # macOS# Windows, Visual Studio 프롬프트에서
cl /LD /MT /O2 /I.. sample_plugin.c /Fe:sample.dllWindows에서 렌더러는 Microsoft가 서명한 DLL과, bauta가 통과시키는 페르소나의 플러그인만 불러옵니다. 그래서
플러그인은 Windows 자체 DLL 외에는 아무것에도 의존하지 않는 DLL 하나여야 합니다. /MT는 C 런타임을 함께
링크합니다. 이것이 없으면 DLL에 Visual C++ 런타임이 필요한데, 런타임이 없는 머신에는 없습니다.
페르소나에서 플러그인 지정
게이트웨이를 거칠 때는 페르소나가 persona.json에서 플러그인을 지정합니다.
{"builtin": "seed:windows:shop", "plugin": "/abs/path/sample.so"}상대 경로는 bauta persona new --from이 읽는 페르소나 파일을 기준으로 풉니다. 그 페르소나의 모든 페이지가 워커를
포함해 플러그인을 실행합니다. 같은 플러그인을 지정한 페르소나끼리 브라우저를 공유하고, 다른 플러그인을
지정했거나 지정하지 않은 페르소나는 브라우저를 따로 씁니다. 플러그인 파일이 없는 페르소나는 시작하지 않으며,
게이트웨이가 이유를 알려 줍니다.
패키지에서는 plugin을 넘깁니다.
browser = launch(plugin="/abs/path/sample.so")const browser = await launch({ plugin: "/abs/path/sample.so" });직접 실행한 브라우저는 환경 변수 BAUTA_PLUGIN=/abs/path/sample.so를 받습니다.
플러그인을 받아들이면 bauta는 stderr에 [bauta] native: loaded <path>를 출력합니다.
예제 플러그인
plugins/examples/sample_plugin.c는 선언형 규칙만으로는 모두 표현할 수 없는 형태들을 보여 줍니다.
- 계산한 값:
navigator.hardwareConcurrency가 페이지와 그 워커에서fib(7)이 됩니다. - 인수로 고른 문자열:
getParameter의 enum에 따라 고른 WebGL 벤더와 렌더러. - 문자열 인수로 고른 답(ABI 2):
CSS.supports("bauta", …)가true가 됩니다. - 반환된 버퍼의 제자리 변환:
getImageData픽셀.
핸들러 규칙
- C 경계 너머로 예외를 던지지 마세요. C++ 플러그인은 모든 예외를 잡아야 합니다.
- 페이지 API를 다시 호출하지 마세요. bauta는 한 단계의 재진입을 막아 주지만, 핸들러는 그 자체로 완결되어야 합니다.
- 잘못된 입력은 무시되고 실제 답이 유지됩니다. null 문자열, 파싱할 수 없는 JSON, 버퍼를 반환하지 않는 호출에 대한 버퍼 요청이 그 예입니다.
- 플러그인 안에서 크래시가 나면 그 렌더러도 크래시합니다. 플러그인은 신뢰하는 코드입니다.
- 페이지의 워커는 같은 멤버를 다른 인터페이스에서 호출합니다.
navigator는WorkerNavigator.*, 워커의 캔버스는OffscreenCanvasRenderingContext2D.*입니다. 이 레이블에도 답하세요. 그렇지 않으면 페이지는 플러그인의 답을 읽는데 워커는 실제 머신을 읽습니다(realm이 누가 물었는지 알려 줍니다). - 자주 쓰이는 일부 멤버에는 V8의 최적화된 코드가 바인딩의 출구를 거치지 않고 지나가는 빠른 경로가 있습니다.
performance.now(), WebGL 그리기와 uniform 호출, 캔버스 경로 호출입니다. 페이지의 코드가 최적화된 뒤에는 이런 멤버에 대한 플러그인의 답이 보장되지 않습니다.
샌드박스
플러그인은 샌드박스를 켠 채로 동작합니다.
- Linux에서는 렌더러 샌드박스가 걸리기 전에 zygote가 플러그인을 매핑합니다.
- Windows에서는 브라우저가 렌더러의 코드 무결성 검사를 통과시키고, 렌더러가 토큰을 낮추기 전에 플러그인을 매핑합니다.
- macOS에서는 렌더러의 샌드박스 프로필이 그 파일 하나를 읽고 매핑하도록 허용합니다.
플러그인은 모든 페이지의 렌더러 안에서 네이티브 코드로 실행됩니다. 브라우저를 신뢰하는 만큼 신뢰하는 플러그인만 불러오세요.