<iframe>
요소는 일반적으로 탐색 컨텍스트 내에 외부 리소스를 삽입하는 데 사용됩니다.
iframe은 교차 출처 삽입 콘텐츠를 호스트 페이지에서 격리하여 웹의 보안 정책을 적용합니다. 이 접근 방식은 출처 간의 보안 경계를 보장하여 보안을 강화하지만 일부 사용 사례를 제한합니다. 예를 들어 교사가 탐색 이벤트를 트리거하여 교실 화면에 웹페이지를 표시하는 등 사용자가 다양한 소스의 콘텐츠를 동적으로 로드하고 관리해야 할 수 있습니다. 하지만 많은 웹사이트에서 X-Frame-Options 및 콘텐츠 보안 정책 (CSP)과 같은 보안 헤더를 사용하여 iframe에 삽입하는 것을 명시적으로 차단합니다.
또한 iframe 제한으로 인해 삽입된 콘텐츠의 탐색 또는 동작을 직접 관리하는 페이지를 삽입할 수 없습니다.
Controlled Frame API는 제한적인 삽입 정책을 적용하더라도 모든 웹 콘텐츠를 로드할 수 있도록 하여 이러한 제한사항을 해결합니다. 이 API는 사용자와 개발자를 잠재적 위험으로부터 보호하기 위한 추가 보안 조치가 통합된 격리된 웹 애플리케이션(IWA) 내에서만 사용할 수 있습니다.
제어된 프레임 구현
제어된 프레임을 사용하려면 먼저 기능적 IWA를 설정해야 합니다. 그런 다음 관리 프레임을 페이지에 통합할 수 있습니다.
권한 정책 추가
관리 프레임을 사용하려면 값 "controlled-frame"
이 있는 permissions_policy
필드를 IWA 매니페스트에 추가하여 해당 권한을 사용 설정하세요.
cross-origin-isolated 키도 포함합니다. 이 키는 관리 프레임에만 적용되는 것이 아니라 모든 IWA에 필요하며 문서가 교차 출처 격리가 필요한 API에 액세스할 수 있는지 여부를 결정합니다.
{
...
"permissions_policy": {
...
"controlled-frame": ["self"],
"cross-origin-isolated": ["self"]
...
}
...
}
분리형 웹 앱 (IWA) 매니페스트의 controlled-frame
키는 권한 정책 허용 목록을 정의하여 제어된 프레임을 사용할 수 있는 출처를 지정합니다. 매니페스트는 전체 권한 정책 구문을 지원하므로 *
, 특정 출처 또는 self
, src
과 같은 키워드를 허용하지만 IWA 전용 API는 다른 출처에 위임할 수 없습니다.
허용 목록에 와일드 카드나 외부 출처가 포함되어 있더라도 이러한 권한은 controlled-frame
와 같은 IWA 기능에 적용되지 않습니다. 표준 웹 앱과 달리 IWA는 정책으로 제어되는 모든 기능을 기본적으로 none으로 설정하므로 명시적 선언이 필요합니다. IWA 관련 기능의 경우 self
(IWA 자체 출처) 또는 src
(삽입된 프레임의 출처)과 같은 값만 기능적으로 유효합니다.
제어된 프레임 요소 추가
HTML에 <controlledframe>
요소를 삽입하여 IWA 내에 서드 파티 콘텐츠를 삽입합니다.
<controlledframe id="controlledframe_1" src="https://example.com">
</controlledframe>
선택적 partition
속성은 삽입된 콘텐츠의 스토리지 파티셔닝을 구성하여 쿠키 및 로컬 스토리지와 같은 데이터를 격리하여 세션 간에 데이터를 유지할 수 있습니다.
예: 인메모리 스토리지 파티션
"session1"
라는 메모리 내 스토리지 파티션을 사용하여 제어된 프레임을 만듭니다. 이 파티션에 저장된 데이터 (예: 쿠키, localStorage)는 프레임이 소멸되거나 애플리케이션 세션이 종료되면 삭제됩니다.
<controlledframe id="controlledframe_1" src="https://example.com">
</controlledframe>
예: 영구 스토리지 파티션
"user_data"
라는 영구 저장소 파티션을 사용하여 제어된 프레임을 만듭니다. "persist:"
접두사는 이 파티션에 저장된 데이터가 디스크에 저장되고 애플리케이션 세션 전반에서 사용할 수 있도록 합니다.
<controlledframe id="frame_2" src="..." partition="persist:user_data">
</controlledframe>
요소 참조 가져오기
표준 HTML 요소처럼 상호작용할 수 있도록 <controlledframe>
요소에 대한 참조를 가져옵니다.
const controlledframe = document.getElementById('controlledframe_1');
자주 발생하는 시나리오 및 사용 사례
일반적으로 불필요한 복잡성을 피하면서 요구사항을 충족하는 최적의 기술을 선택하세요. 최근 몇 년간 프로그레시브 웹 앱(PWA)이 네이티브 앱과의 격차를 좁혀 강력한 웹 환경을 지원하고 있습니다. 웹 애플리케이션이 서드 파티 콘텐츠를 삽입해야 하는 경우 먼저 일반적인 <iframe>
접근 방식을 살펴보는 것이 좋습니다. 요구사항이 iframe의 기능을 초과하는 경우 IWA의 제어된 프레임이 최적의 대안일 수 있습니다. 일반적인 사용 사례는 다음 섹션에 설명되어 있습니다.
서드 파티 웹 콘텐츠 삽입
많은 애플리케이션은 사용자 인터페이스 내에서 서드 파티 콘텐츠를 로드하고 표시할 수 있어야 합니다. 하지만 여러 웹 앱 소유자가 관련된 경우(삽입된 애플리케이션의 일반적인 시나리오) 일관된 엔드 투 엔드 정책을 설정하기가 어려워집니다. 예를 들어 보안 설정으로 인해 비즈니스에 합법적인 필요가 있는 경우에도 기존 <iframe>
가 특정 유형의 콘텐츠를 삽입하지 못할 수 있습니다. <iframe>
요소와 달리 관리 프레임은 이러한 제한을 우회하도록 설계되어 애플리케이션이 표준 삽입을 명시적으로 금지하더라도 콘텐츠를 로드하고 표시할 수 있습니다.
사용 사례
- 클래스룸 프레젠테이션: 교사가 클래스룸 터치 스크린을 사용하여 일반적으로 iframe 삽입을 차단하는 교육 리소스 간에 전환합니다.
- 소매업체 또는 쇼핑몰의 디지털 사이니지: 쇼핑몰 키오스크에서 다양한 매장의 웹사이트를 순환합니다. 제어된 프레임은 이러한 페이지가 삽입을 제한하더라도 올바르게 로드되도록 합니다.
코드 샘플
다음 Controlled Frame API는 삽입된 콘텐츠를 관리하는 데 유용합니다.
탐색: 제어된 프레임은 삽입된 콘텐츠의 탐색 및 탐색 기록을 프로그래매틱 방식으로 관리하고 제어하는 여러 방법을 제공합니다.
src
속성은 프레임에 표시된 콘텐츠의 URL을 가져오거나 설정하며 HTML 속성과 동일한 방식으로 작동합니다.
controlledframe.src = "https://example.com";
back()
메서드는 프레임의 기록에서 한 단계 뒤로 이동합니다. 반환된 프로미스는 탐색이 성공했는지 여부를 나타내는 불리언으로 확인됩니다.
document.getElementById('backBtn').addEventListener('click', () => {
controlledframe.back().then((success) => {
console.log(`Back navigation ${success ? 'succeeded' : 'failed'}`); }).catch((error) => {
console.error('Error during back navigation:', error);
});
});
forward()
메서드는 프레임의 기록에서 한 단계 앞으로 이동합니다. 반환된 프로미스는 탐색이 성공했는지 여부를 나타내는 불리언으로 확인됩니다.
document.getElementById('forwardBtn').addEventListener('click', () => {
controlledframe.forward().then((success) => {
console.log(`Forward navigation ${success ? 'succeeded' : 'failed'}`);
}).catch((error) => {
console.error('Error during forward navigation:', error);
});
});
reload()
메서드는 프레임에서 현재 페이지를 새로고침합니다.
document.getElementById('reloadBtn').addEventListener('click', () => {
controlledframe.reload();
});
또한 관리 프레임은 시작 및 리디렉션부터 콘텐츠 로드, 완료 또는 중단에 이르기까지 탐색 요청의 전체 수명 주기를 추적할 수 있는 이벤트를 제공합니다.
loadstart
: 프레임에서 탐색이 시작될 때 발생합니다.loadcommit
: 탐색 요청이 처리되고 기본 문서 콘텐츠가 로드되기 시작할 때 발생합니다.contentload
: 기본 문서와 필수 리소스의 로드가 완료되면 발생합니다 (DOMContentLoaded와 유사).loadstop
: 페이지의 모든 리소스 (하위 프레임, 이미지 포함)가 로드를 완료하면 실행됩니다.loadabort
: 탐색이 중단된 경우 (예: 사용자 작업이나 다른 탐색이 시작됨) 발생합니다.loadredirect
: 탐색 중에 서버 측 리디렉션이 발생할 때 실행됩니다.
controlledframe.addEventListener('loadstart', (event) => {
console.log('Navigation started:', event.url);
// Example: Show loading indicator
});
controlledframe.addEventListener('loadcommit', (event) => {
console.log('Navigation committed:', event.url);
});
controlledframe.addEventListener('contentload', (event) => {
console.log('Content loaded for:', controlledframe.src);
// Example: Hide loading indicator, maybe run initial script
});
controlledframe.addEventListener('loadstop', (event) => {
console.log('All resources loaded for:', controlledframe.src);
});
controlledframe.addEventListener('loadabort', (event) => {
console.warn(`Navigation aborted: ${event.url}, Reason: ${event.detail.reason}`);
});
controlledframe.addEventListener('loadredirect', (event) => {
console.log(`Redirect detected: ${event.oldUrl} -> ${event.newUrl}`);
});
또한 제어된 프레임 내에서 로드된 콘텐츠에 의해 시작된 특정 상호작용이나 요청(예: 대화상자를 열거나, 권한을 요청하거나, 새 창을 열려는 시도)을 모니터링하고 잠재적으로 차단할 수 있습니다.
dialog
: 삽입된 콘텐츠가 대화상자 (알림, 확인, 프롬프트)를 열려고 시도할 때 발생합니다. 세부정보를 확인하고 응답할 수 있습니다.consolemessage
: 프레임 내에서 메시지가 콘솔에 로깅될 때 발생합니다.permissionrequest
: 삽입된 콘텐츠가 권한(예: 위치정보 및 알림)을 요청할 때 발생합니다. 세부정보를 확인하고 요청을 허용하거나 거부할 수 있습니다.newwindow
: 삽입된 콘텐츠가 새 창이나 탭을 열려고 시도할 때 (예: window.open 또는target="_blank"
이 있는 링크) 발생합니다. 세부정보를 수신하고 작업을 처리하거나 차단할 수 있습니다.
controlledframe.addEventListener('dialog', (event) => {
console.log(Dialog opened: Type=${event.messageType}, Message=${event.messageText});
// You will need to respond, e.g., event.dialog.ok() or .cancel()
});
controlledframe.addEventListener('consolemessage', (event) => {
console.log(Frame Console [${event.level}]: ${event.message});
});
controlledframe.addEventListener('permissionrequest', (event) => {
console.log(Permission requested: Type=${event.permission});
// You must respond, e.g., event.request.allow() or .deny()
console.warn('Permission request needs handling - Denying by default');
if (event.request && event.request.deny) {
event.request.deny();
}
});
controlledframe.addEventListener('newwindow', (event) => {
console.log(New window requested: URL=${event.targetUrl}, Name=${event.name});
// Decide how to handle this, e.g., open in a new controlled frame and call event.window.attach(), ignore, or block
console.warn('New window request needs handling - Blocking by default');
});
제어된 프레임의 자체 렌더링 상태와 관련된 변경사항(예: 크기 또는 확대/축소 수준 수정)을 알리는 상태 변경 이벤트도 있습니다.
sizechanged
: 프레임 콘텐츠의 크기가 변경될 때 발생합니다.zoomchange
: 프레임 콘텐츠의 확대/축소 수준이 변경될 때 발생합니다.
controlledframe.addEventListener('sizechanged', (event) => {
console.log(Frame size changed: Width=${event.width}, Height=${event.height});
});
controlledframe.addEventListener('zoomchange', (event) => {
console.log(Frame zoom changed: Factor=${event.newZoomFactor});
});
스토리지 메서드: 관리 프레임은 프레임의 파티션 내에 저장된 데이터를 관리하는 API를 제공합니다.
clearData()
를 사용하여 저장된 모든 데이터를 삭제합니다. 이는 사용자 세션 후 프레임을 재설정하거나 깨끗한 상태를 유지하는 데 특히 유용합니다. 이 메서드는 작업이 완료되면 확인되는 Promise를 반환합니다. 선택적 구성 옵션도 제공할 수 있습니다.
types
: 삭제할 데이터 유형을 지정하는 문자열 배열입니다 (예:['cookies', 'localStorage', 'indexedDB']
). 생략하면 일반적으로 적용 가능한 모든 데이터 유형이 삭제됩니다.options
: since 속성(에포크 이후의 타임스탬프(밀리초))을 사용하여 특정 시간 이후에 생성된 데이터만 삭제하는 등 삭제 프로세스를 제어합니다.
예: 관리 프레임과 연결된 모든 스토리지 삭제
function clearAllPartitionData() {
console.log('Clearing all data for partition:', controlledframe.partition);
controlledframe.clearData()
.then(() => {
console.log('Partition data cleared successfully.');
})
.catch((error) => {
console.error('Error clearing partition data:', error);
});
}
예: 지난 1시간 동안 생성된 쿠키 및 localStorage만 삭제
function clearRecentCookiesAndStorage() {
const oneHourAgo = Date.now() - (60 * 60 * 1000);
const dataTypesArray = ['cookies', 'localStorage'];
const dataTypesToClearObject = {};
for (const type of dataTypesArray) {
dataTypesToClearObject[type] = true;
}
const clearOptions = { since: oneHourAgo };
console.log(`Clearing ${dataTypesArray.join(', ')} since ${new Date(oneHourAgo).toISOString()}`); controlledframe.clearData(clearOptions, dataTypesToClearObject) .then(() => {
console.log('Specified partition data cleared successfully.');
}).catch((error) => {
console.error('Error clearing specified partition data:', error);
});
}
서드 파티 애플리케이션 확장 또는 변경
단순한 삽입을 넘어 제어된 프레임은 삽입 IWA가 삽입된 서드 파티 웹 콘텐츠를 제어할 수 있는 메커니즘을 제공합니다. 안전하고 격리된 환경에서 삽입된 콘텐츠 내에서 스크립트를 실행하고, 네트워크 요청을 가로채고, 기본 컨텍스트 메뉴를 재정의할 수 있습니다.
사용 사례
- 서드 파티 사이트 전반에 브랜딩 적용: 삽입된 웹사이트에 맞춤 CSS 및 JavaScript를 삽입하여 통합된 시각적 테마를 적용합니다.
- 탐색 및 링크 동작 제한: 스크립트 삽입으로 특정
<a>
태그 동작을 가로채거나 사용 중지합니다. - 비정상 종료 또는 비활성 상태 후 복구 자동화: 실패 상태 (예: 빈 화면, 스크립트 오류)에 대해 삽입된 콘텐츠를 모니터링하고 제한 시간 후 프로그래매틱 방식으로 세션을 다시 로드하거나 재설정합니다.
코드 샘플
스크립트 삽입: executeScript()
를 사용하여 제어된 프레임에 JavaScript를 삽입하여 동작을 맞춤설정하거나, 오버레이를 추가하거나, 삽입된 서드 파티 페이지에서 데이터를 추출할 수 있습니다. 인라인 코드를 문자열로 제공하거나 하나 이상의 스크립트 파일을 참조할 수 있습니다 (IWA 패키지 내의 상대 경로 사용). 이 메서드는 스크립트 실행 결과(일반적으로 마지막 문의 값)로 확인되는 프로미스를 반환합니다.
document.getElementById('scriptBtn').addEventListener('click', () => {
controlledframe.executeScript({
code: `document.body.style.backgroundColor = 'lightblue';
document.querySelectorAll('a').forEach(link => link.style.pointerEvents = 'none');
document.title; // Return a value
`,
// You can also inject files:
// files: ['./injected_script.js'],
}) .then((result) => {
// The result of the last statement in the script is usually returned.
console.log('Script execution successful. Result (e.g., page title):', result); }).catch((error) => {
console.error('Script execution failed:', error);
});
});
스타일 삽입: insertCSS()
를 사용하여 관리 프레임 내에 로드된 페이지에 맞춤 스타일을 적용합니다.
document.getElementById('cssBtn').addEventListener('click', () => {
controlledframe.insertCSS({
code: `body { font-family: monospace; }`
// You can also inject files:
// files: ['./injected_styles.css']
})
.then(() => {
console.log('CSS injection successful.');
})
.catch((error) => {
console.error('CSS injection failed:', error);
});
});
네트워크 요청 가로채기: WebRequest API를 사용하여 요청을 차단하거나, 헤더를 변경하거나, 사용량을 로깅하는 등 삽입된 페이지의 네트워크 요청을 관찰하고 수정할 수 있습니다.
// Get the request object
const webRequest = controlledframe.request;
// Create an interceptor for a specific URL pattern
const interceptor = webRequest.createWebRequestInterceptor({
urlPatterns: ["*://evil.com/*"],
blocking: true,
includeHeaders: "all"
});
// Add a listener to block the request
interceptor.addEventListener("beforerequest", (event) => {
console.log('Blocking request to:', event.url);
event.preventDefault();
});
// Add a listener to modify request headers
interceptor.addEventListener("beforesendheaders", (event) => {
console.log('Modifying headers for:', event.url);
const newHeaders = new Headers(event.headers);
newHeaders.append('X-Custom-Header', 'MyValue');
event.setRequestHeaders(newHeaders);
});
맞춤 컨텍스트 메뉴 추가: contextMenus
API를 사용하여 삽입된 프레임 내에서 맞춤 오른쪽 클릭 메뉴를 추가, 삭제, 처리합니다. 이 예에서는 제어된 프레임 내에 맞춤 '선택 항목 복사' 메뉴를 추가하는 방법을 보여줍니다. 텍스트를 선택하고 사용자가 마우스 오른쪽 버튼을 클릭하면 메뉴가 표시됩니다. 이 버튼을 클릭하면 선택한 텍스트가 클립보드에 복사되어 삽입된 콘텐츠 내에서 간단하고 사용자 친화적인 상호작용이 가능합니다.
const menuItemProperties = {
id: "copy-selection",
title: "Copy selection",
contexts: ["selection"],
documentURLPatterns: [new URLPattern({ hostname: '*.example.com'})]
};
// Create the context menu item using a promise
try {
await controlledframe.contextMenus.create(menuItemProperties);
console.log(`Context menu item "${menuItemProperties.id}" created successfully.`);
} catch (error) {
console.error(`Failed to create context menu item:`, error);
}
// Add a standard event listener for the 'click' event
controlledframe.contextMenus.addEventListener('click', (event) => {
if (event.menuItemId === "copy-selection" && event.selectionText) {
navigator.clipboard.writeText(event.selectionText)
.then(() => console.log("Text copied to clipboard."))
.catch(err => console.error("Failed to copy text:", err));
}
});
데모
Controlled Frame 데모에서 Controlled Frame의 메서드를 간략하게 살펴보세요.
또는 IWA Kitchen Sink에는 여러 탭이 있는 앱이 포함되어 있으며 각 탭에서는 제어된 프레임, 직접 소켓 등 다양한 IWA API를 보여줍니다.
결론
제어된 프레임은 분리형 웹 앱 (IWA)에서 서드 파티 웹 콘텐츠를 삽입, 확장하고 상호작용할 수 있는 강력하고 안전한 방법을 제공합니다. iframe의 제한사항을 극복함으로써 삽입된 콘텐츠 내에서 스크립트를 실행하고, 네트워크 요청을 가로채고, 맞춤 컨텍스트 메뉴를 구현하는 등의 새로운 기능을 엄격한 격리 경계를 유지하면서 사용할 수 있습니다. 하지만 이러한 API는 삽입된 콘텐츠를 세부적으로 제어할 수 있으므로 추가 보안 제약이 적용되며 사용자 및 개발자 모두에게 더 강력한 보장을 제공하도록 설계된 IWA 내에서만 사용할 수 있습니다. 대부분의 사용 사례에서 개발자는 먼저 표준 <iframe>
요소를 사용하는 것을 고려해야 합니다. 표준 <iframe>
요소는 더 간단하며 많은 시나리오에서 충분합니다. 제어된 프레임은 iframe 기반 솔루션이 삽입 제한으로 차단되거나 필요한 제어 및 상호작용 기능이 부족한 경우 평가해야 합니다. 키오스크 환경을 빌드하든, 서드 파티 도구를 통합하든, 모듈식 플러그인 시스템을 설계하든, 제어된 프레임을 사용하면 구조화되고 권한이 부여된 보안 환경에서 세부적인 제어가 가능하므로 차세대 고급 웹 애플리케이션에서 중요한 도구가 됩니다.