(부제: 내가 확장프로그램을 선택할 수 밖에 없는 이유 1만가지중 하나)
웹앱에 다른 사이트를 iframe으로 삽입했을 때, iframe 내부에 포커스가 있으면 웹앱의 단축키가 먹히지 않는다. Cross-Origin이라 iframe의 이벤트에 접근할 수도 없다. 이 문제를 키 이벤트 재합성으로 해결한 방법을 정리했다.
문제 정의
상황
- 웹앱이 외부 사이트를 iframe으로 로드
react-hotkeys-hook으로 단축키 처리- iframe 클릭 시 포커스가 iframe 내부로 이동
- 이후 웹앱의 단축키가 전혀 작동하지 않음
왜 안 될까?
// 웹앱에서
iframe.contentWindow.addEventListener('keydown', handler);
// → Blocked a frame with origin "https://webapp.com" from accessing
// a cross-origin frame.
Same-Origin Policy로 인해 Cross-Origin iframe의 DOM, 이벤트, 변수에 접근할 수 없다. 보안상 당연하고, 우회하면 안 된다.
하지만 이벤트를 "직접 접근"하지 않고 "전달받는" 것은 가능하다.
선택지 분석
| 방식 | 장점 | 한계 | 적합한 상황 |
|---|---|---|---|
| 프록시 서버 | Same-Origin으로 만들 수 있음 | 트래픽 증가, 속도 저하 | 완전 제어 필요 시 |
| window.postMessage | 표준 API, 안전 | iframe에서 코드 실행 필요 | Extension 활용 가능 시 |
| iframe의 sandbox | 부분적 제어 가능 | 기능 제한 큼 | 신뢰할 수 없는 콘텐츠 |
| 포기 | - | - | - |
Chrome Extension을 사용하는 프로젝트라 Content Script를 iframe에 주입할 수 있다. postMessage 방식이 가장 현실적이다.
선택 근거
postMessage 기반 키 이벤트 재합성을 선택했다.
- Content Script가 iframe 내부에 주입됨
- iframe 내부에서 키 이벤트 발생
- postMessage로 parent에게 이벤트 정보 전달
- parent가 동일한 KeyboardEvent를 재생성
- react-hotkeys-hook이 이를 감지
이 방식은 Same-Origin Policy를 위반하지 않으면서 키 이벤트를 전달한다.
구현
1. iframe 내부: 키 이벤트 캡처 및 전달
Content Script가 iframe 내부에서 키 이벤트를 잡아 parent로 전송한다.
// extension/content-scripts/iframe/handlers/keyboard-handler.ts
function forwardKeyEvent(e: KeyboardEvent, eventType: 'keydown' | 'keyup'): void {
window.parent.postMessage(
{
type: 'PIXELDIFF_KEY_EVENT',
source: 'pixeldiff-extension',
eventType,
key: e.key,
code: e.code,
altKey: e.altKey,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
},
PARENT_ORIGIN
);
}
window.addEventListener('keydown', (e) => {
if (shouldForwardKeyEvent(e)) {
forwardKeyEvent(e, 'keydown');
}
});
2. 입력 필드 보호
텍스트를 입력할 때는 키 이벤트를 전달하면 안 된다.
function shouldForwardKeyEvent(e: KeyboardEvent): boolean {
const target = e.target as HTMLElement;
// input, textarea는 제외
if (target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement) {
return false;
}
// contentEditable 요소도 제외
if (target.isContentEditable) {
return false;
}
return true;
}
이 검사가 없으면 iframe 내부의 검색창에서 타이핑할 때마다 웹앱 단축키가 실행된다.
3. Parent Origin 안전하게 얻기
Cross-Origin iframe에서 parent의 origin을 직접 알 수 없다.
// extension/content-scripts/iframe/services/parent-origin.ts
export function getParentOrigin(): string {
try {
// Chrome의 ancestorOrigins API 사용
if (window.location.ancestorOrigins && window.location.ancestorOrigins.length > 0) {
return window.location.ancestorOrigins[0];
}
// 실패 시 wildcard
return '*';
} catch {
return '*';
}
}
window.location.ancestorOrigins는 Chrome에서 제공하는 API로, iframe이 자신의 조상 origin을 알 수 있다. 지원하지 않는 브라우저에서는 *를 사용한다.
4. 웹앱: 이벤트 수신 및 재합성
// apps/web/hooks/useIframeMessages.ts
const handleMessage = (e: MessageEvent) => {
// source 검증 - 중요!
if (e.data.source !== 'pixeldiff-extension') return;
if (e.data.type === 'PIXELDIFF_KEY_EVENT') {
const data = e.data as KeyEventData;
// 동일한 KeyboardEvent 재생성
const event = new KeyboardEvent(data.eventType, {
key: data.key,
code: data.code,
altKey: data.altKey,
ctrlKey: data.ctrlKey,
metaKey: data.metaKey,
shiftKey: data.shiftKey,
bubbles: true,
cancelable: true,
});
// document에 dispatch
document.dispatchEvent(event);
}
};
window.addEventListener('message', handleMessage);
핵심은 document.dispatchEvent(event)다. react-hotkeys-hook은 document 레벨에 이벤트 리스너를 등록하므로, 여기에 이벤트를 dispatch하면 단축키가 작동한다.
5. 데이터 흐름
[iframe 내부 - example.com]
↓ keydown 발생
keyboard-handler.ts
↓ postMessage
[Parent - webapp.com]
↓ message 이벤트
useIframeMessages.ts
↓ source 검증
↓ new KeyboardEvent(...)
document.dispatchEvent(event)
↓
react-hotkeys-hook 감지
↓
단축키 핸들러 실행
보안 고려사항
wildcard origin은 위험하지 않나?
postMessage(data, '*')는 보통 위험하다고 알려져 있다. 하지만 여기서는 안전하다.
// 수신 측에서 source 검증
if (e.data.source !== 'pixeldiff-extension') return;
메시지를 보내는 쪽이 아니라 받는 쪽에서 검증한다. 악의적인 iframe이 메시지를 보내도 source 필드가 일치하지 않으면 무시된다.
왜 이렇게 했나?
ancestorOrigins가 없는 브라우저에서도 동작해야 한다. wildcard를 쓰더라도 수신 측 검증으로 보안을 유지할 수 있다.
Cross-Origin iframe의 이벤트에 접근하는 것은 불가능하지만, 전달받는 것은 가능하다.
| 단계 | 위치 | 역할 |
|---|---|---|
| 캡처 | iframe 내부 | Content Script가 키 이벤트 잡음 |
| 전달 | postMessage | 이벤트 정보를 parent로 전송 |
| 재합성 | parent | 동일한 KeyboardEvent 생성 |
| 처리 | document | react-hotkeys-hook이 감지 |
이 패턴은 iframe을 사용하는 웹앱에서 전역 단축키를 유지해야 할 때 유용하다. Extension 없이도 iframe 내부에 스크립트를 주입할 수 있는 상황이라면 같은 방식을 적용할 수 있다.
프런트엔드 엔지니어, QA 엔지니어 그리고 디자이너를 위한
" ALL IN ONE " QA 서비스
https://pixeldiff.turtle-tail.com
'FrontEnd' 카테고리의 다른 글
| OffscreenCanvas로 Service Worker에서 이미지 처리하기 (0) | 2026.02.10 |
|---|---|
| next-intl 없이 i18n 직접 만들기 (0) | 2026.02.07 |
| Sharp + WebP로 이미지 30-50% 용량 절감하기 (0) | 2026.01.27 |
| S3 Presigned URL로 클라이언트 직접 업로드 (0) | 2026.01.22 |
| 풀페이지 스크린샷 타일 스티칭 구현 (0) | 2026.01.20 |
