1. 소개
하위 앱 API를 사용하면 상위 애플리케이션이 다음과 같은 보조 애플리케이션(하위 앱)을 프로그래밍 방식으로 설치하고, 나열하고, 제거할 수 있다.
-
운영 체제와 사용자에게 완전히 별개의 애플리케이션으로 표시된다(별도의 런처 아이콘, 고유한 작업 표시줄/선반 창 및 개별 OS 통합).
-
상위 애플리케이션의 기반 리소스, 출처, 저장소, 권한 및 업데이트 수명 주기를 공유한다.
보안과 데이터 무결성을 보장하기 위해 이 API는 격리된 컨텍스트로 제한된다.
2. 개념
설치된 웹 애플리케이션에는 연결된 상위 앱이 있으며, 이는 null이거나 설치된 웹 애플리케이션이다.
설치된 웹 애플리케이션에는 연결된 하위 앱 집합이 있으며, 이는 집합 형태의 설치된 웹 애플리케이션들이다.
Document에는
연결된 설치된 웹 애플리케이션이 있으며, 이는 해당 Document가
일부로 표시되는 설치된 웹 애플리케이션이다. 이 연결에 대한 정확한 메커니즘은 구현 정의이다.
Document에
연결된 설치된 웹 애플리케이션이 있고 그
연결된 설치된 웹 애플리케이션의
상위 앱이 null이 아니면 하위 앱
문서이다.
3. Window
인터페이스의 확장
[Exposed =Window ,SecureContext ,IsolatedContext ]partial interface Window { [SameObject ]readonly attribute SubApps subApps ; };
3.1. subApps
속성
각 Window
객체에는 연결된 subApps가 있으며, 이는 해당 Window와
함께 생성된 SubApps
인스턴스이다.
4. SubApps
인터페이스
// https://w3c.github.io/manifest/#id-member를 나타낸다typedef USVString ;ManifestId dictionary {SubAppsAddResponse record <USVString ,ManifestId >;installedApps record <USVString ,DOMException >; };failedApps dictionary {SubAppsRemoveResponse sequence <ManifestId >;removedApps record <USVString ,DOMException >; };failedApps dictionary {SubAppsListResult required DOMString ; }; [appName Exposed =Window ,SecureContext ,IsolatedContext ]interface {SubApps Promise <SubAppsAddResponse >(add sequence <USVString >);install_paths Promise <SubAppsRemoveResponse >(remove sequence <ManifestId >);manifest_ids Promise <record <USVString ,SubAppsListResult >>(); };list
다음 조건을 모두 충족하는 경우 문자열 path는 Document
document에 대한 유효한 상대 경로이다.
-
path는 유효한 절대 URL이 아니다.
-
path는
"/"로 시작한다. -
path는 비어 있지 않다.
-
path는
"//"로 시작하지 않는다.
4.1. add()
메서드
add(install_paths)
메서드 단계는 다음과 같다.
install_paths
인수는 하위 앱의 시작 HTML 페이지를 가리키는 상대 경로 목록이다.
-
promise를 새 promise로 둔다.
-
document를 관련 전역 객체의 연결된 Document로 둔다.
-
document에 "sub-apps"라는 이름의 정책 제어 기능을 사용할 권한이 없다면, promise를 "
SecurityError"DOMException으로 거부하고 promise를 반환한다. -
document가 하위 앱 문서라면, promise를 "
NotSupportedError"DOMException으로 거부하고 promise를 반환한다. -
parsedUrls를 빈 목록으로 둔다.
-
install_paths의 각 installPath에 대해 다음을 수행한다:-
installPath가 document에 대한 유효한 상대 경로가 아니라면, promise를 "
TypeError"DOMException으로 거부하고 promise를 반환한다. -
absoluteUrl을 document의 문서 기준 URL을 기준 URL로 사용하여 installPath를 파싱한 결과로 둔다.
-
absoluteUrl이 실패라면, promise를 "
TypeError"DOMException으로 거부하고 promise를 반환한다. -
absoluteUrl을 parsedUrls에 추가한다.
-
-
parentApp을 document의 연결된 설치된 웹 애플리케이션으로 둔다.
-
install_paths의 크기가 20보다 크다면, promise를 "QuotaExceededError"DOMException으로 거부하고 promise를 반환한다. -
currentSubAppsCount +
install_paths의 크기가 50보다 크다면, promise를 "QuotaExceededError"DOMException으로 거부하고 promise를 반환한다. -
subApps를 this로 둔다.
-
다음 단계를 병렬로 실행한다:
-
userConsent를
install_paths의 하위 앱 설치에 대한 사용자 동의를 요청한 결과로 둔다 (예: 통합 설치 대화 상자를 표시하는 방식). -
userConsent가 거부되었다면, subApps의 관련 전역 객체에서 promise를 "
NotAllowedError"DOMException으로 거부하는 전역 작업을 큐에 넣고, 이 단계들을 중단한다. -
installedApps를 빈 맵으로 둔다.
-
failedApps를 빈 맵으로 둔다.
-
parsedUrls의 각 absoluteUrl에 대해 다음을 수행한다:
-
installPath를 absoluteUrl의 경로로 둔다.
-
manifest를 absoluteUrl이 주어졌을 때 매니페스트를 가져와 처리한 결과로 둔다.
-
manifest가 실패라면, 다음 단계를 수행한다:
-
failedApps[installPath]를 새 "
DataError"DOMException으로 설정한다. -
계속한다.
-
-
manifestId를 manifest의 id로 둔다. 이것이 정의되지 않았다면, manifest의 start_url을 대신 사용한다(참조/해시 조각 제외).
-
parentApp에 manifestId를 사용하는 하위 앱이 이미 설치되어 있다면, 다음 단계를 수행한다:
-
failedApps[installPath]를 새 "
InvalidStateError"DOMException으로 설정한다. -
계속한다.
-
-
parentApp의 매니페스트의 범위가 manifest의 범위의 접두사이거나, manifest의 범위가 parentApp의 하위 앱 집합에 현재 설치된 하위 앱 중 하나의 범위의 접두사이거나, parentApp의 하위 앱 집합에 현재 설치된 하위 앱 중 하나의 범위가 manifest의 범위의 접두사이거나, absoluteUrl이 parentApp 자체의 매니페스트를 가리킨다면, 다음 단계를 수행한다:
-
failedApps[installPath]를 새 "
ConstraintError"DOMException으로 설정한다. -
계속한다.
-
-
플랫폼의 애플리케이션 실행기에 하위 앱 설치를 시도한다.
-
시스템 또는 데이터베이스 오류로 인해 설치에 실패한다면:
-
failedApps[installPath]를 새 "
OperationError"DOMException으로 설정한다. -
계속한다.
-
-
installedApps[installPath]를 manifestId로 설정한다.
-
-
response를 다음 항목이 포함된 새
SubAppsAddResponse사전으로 둔다:-
installedApps를 installedApps로 설정한다. -
failedApps를 failedApps로 설정한다.
-
-
subApps의 관련 전역 객체에서 promise를 response로 이행하는 전역 작업을 큐에 넣는다.
-
-
promise를 반환한다.
4.2. remove()
메서드
manifest_ids
인수는 제거할 하위 앱의 id 목록이다.
remove(manifest_ids)
메서드 단계는 다음과 같다.
-
promise를 새 promise로 둔다.
-
document를 관련 전역 객체의 연결된 Document로 둔다.
-
document가 "sub-apps"라는 이름의 정책 제어 기능을 사용하도록 허용되지 않았다면, promise를 "
SecurityError"DOMException으로 거부하고 promise를 반환한다. -
document가 하위 앱 문서라면, promise를 "
NotSupportedError"DOMException으로 거부하고 promise를 반환한다. -
parentApp을 document의 연결된 설치된 웹 애플리케이션으로 둔다.
-
parsedManifestIds를 빈 목록으로 둔다.
-
manifest_ids의 각 manifestId에 대해 다음을 수행한다:-
manifestId가 document에 대한 유효한 상대 경로가 아니라면, promise를 "
TypeError"DOMException으로 거부하고 promise를 반환한다. -
parsedUrl을 document의 문서 기준 URL을 기준 URL로 사용하여 manifestId를 파싱한 결과로 둔다.
-
parsedUrl이 실패라면, promise를 "
TypeError"DOMException으로 거부하고 promise를 반환한다. -
parsedUrl을 parsedManifestIds에 추가한다.
-
-
subApps를 this로 둔다.
-
다음 단계를 병렬로 실행한다:
-
removedApps를 빈 시퀀스로 둔다.
-
failedApps를 빈 맵으로 둔다.
-
parsedManifestIds의 각 parsedUrl에 대해 다음을 수행한다:
-
manifestId를 parsedUrl의 경로로 둔다.
-
parentApp의 하위 앱 집합에 id가 manifestId인 설치된 웹 애플리케이션이 없다면, 다음 단계를 수행한다:
-
failedApps[manifestId]를 새 "
NotFoundError"DOMException으로 설정한다. -
계속한다.
-
-
시스템 실행기와 레지스트리에서 ID가 manifestId인 하위 앱의 제거를 시도한다.
-
시스템 오류로 인해 제거에 실패한다면, 다음 단계를 수행한다:
-
failedApps[manifestId]를 새 "
OperationError"DOMException으로 설정한다. -
계속한다.
-
-
manifestId를 removedApps에 추가한다.
-
-
response를 다음 항목이 포함된 새
SubAppsRemoveResponse사전으로 둔다:-
removedApps를 removedApps로 설정한다. -
failedApps를 failedApps로 설정한다.
-
-
subApps의 관련 전역 객체에서 promise를 response로 이행하도록 전역 작업을 큐에 넣는다.
-
-
promise를 반환한다.
4.3. list()
메서드
list()
메서드 단계는 다음과 같다.
-
promise를 새 promise로 둔다.
-
document를 관련 전역 객체의 연결된 Document로 둔다.
-
document가 "sub-apps"라는 이름의 정책 제어 기능을 사용하도록 허용되지 않았다면, promise를 "
SecurityError"DOMException으로 거부하고 promise를 반환한다. -
document가 하위 앱 문서라면, promise를 "
NotSupportedError"DOMException으로 거부하고 promise를 반환한다. -
parentApp을 document의 연결된 설치된 웹 애플리케이션으로 둔다.
-
subApps를 this로 둔다.
-
다음 단계를 병렬로 실행한다:
-
listResult를 빈 맵으로 둔다.
-
플랫폼 레지스트리에서 parentApp에 현재 설치된 모든 하위 앱의 목록을 가져온다.
-
플랫폼 오류로 인해 목록을 가져오지 못했다면, 전역 작업을 큐에 넣는다. subApps의 관련 전역 객체에서 promise를 "
OperationError"DOMException으로 거부하도록 하고, 이 단계들을 중단한다. -
설치된 각 하위 앱 subApp에 대해 다음을 수행한다:
-
manifestId를 subApp의 id로 둔다.
-
appName을 하위 앱의 웹 매니페스트에서 추출한 이름으로 둔다.
-
resultEntry를
SubAppsListResult사전의 새 인스턴스로 두고,appName을 appName으로 설정한다. -
listResult[manifestId]를 resultEntry로 설정한다.
-
-
전역 작업을 큐에 넣는다. subApps의 관련 전역 객체에서 promise를 listResult로 이행하도록 한다.
-
-
promise를 반환한다.
4.4. 매니페스트 가져오기 및 처리
"매니페스트를 가져와 처리" 알고리즘을 작성한다. [이슈 #2]
url(URL)이 주어졌을 때 매니페스트를 가져와 처리하려면 다음 단계를 실행한다.
-
실패를 반환한다.
5. 보안 및 개인정보 보호 고려 사항
보조 애플리케이션을 설치하고 관리하는 것은 강력한 기능이다. 사용자 에이전트는 웹 애플리케이션이 명시적 권한 없이 하위 앱을 설치하거나 관리하도록 허용해서는 안 된다.
이 절에서는 고려되는 위협과 이를 완화하기 위한 사용자 에이전트의 규범적 요구 사항을 설명한다.
5.1. 공유 출처 ID
하위 앱은 별도의 보안 출처를 갖지 않는다. 하위 앱은 상위 앱과 정확히 동일한 출처 및 로컬 데이터 저장소(예: 쿠키, IndexedDB, LocalStorage, Cache Storage)를 공유한다. 표준 웹 보안 경계(예: 동일 출처 정책)는 상위 앱과 모든 하위 앱을 하나의 엔터티로 취급한다.5.2. 권한 상속
모든 권한은 상위 앱과 하위 앱 간에 공유된다. 하위 앱에 권한(예: 카메라, 파일 시스템 접근, USB)을 부여하면 상위 앱에도 자동으로 부여되며, 그 반대도 마찬가지이다.하위 앱 API에 접근하려면 상위 앱의 문서에서 권한 정책
sub-apps를 명시적으로 선언해야 한다. 하위 앱에 선언된 권한 정책은 아무런 효과가 없다.
5.3. 명시적인 사용자 동의
특정 출처에 대해 사용자 동의를 얻어야 한다.add()가
호출되면 사용자 에이전트는 요청된 모든 하위 앱을 표시하는 통합 설치 대화 상자를 사용자에게 표시해야 한다.
여러 하위 앱을 한 번에 추가하는 경우 대화 상자 스팸을 방지하기 위해 하나의 프롬프트 안에 표시하는 것이 좋다.
사용자 에이전트는 어느 출처가 접근을 요청하는지 명확하게 나타내고 사용자가 충분한 정보를 바탕으로 결정을 내릴 수 있도록 충분한 정보(예: 설치되는 하위 앱의 이름과 아이콘)를 제공하는 권한 프롬프트를 표시해야 한다.
5.4. ID 스푸핑 위험
개발자가 하위 앱의 이름과 아이콘을 사용자 지정할 수 있으므로, 악성 애플리케이션이 시스템 대화 상자나 신뢰할 수 있는 타사 애플리케이션을 모방하는 하위 앱을 만들 수 있는 위험이 있다. 이 위험을 완화하기 위해 하위 앱 API는 무결성과 서명 검증을 보장하는 격리된 컨텍스트로 제한된다.5.5. OS 통합 확장 위험
하위 앱은 자체 OS 통합(예: 프로토콜 처리기 또는 파일 형식 연결)을 등록할 수 있다. 이는 애플리케이션이 상위 앱의 기본 매니페스트에 선언된 범위를 훨씬 넘어 OS까지 영향을 확장할 수 있음을 의미한다. 대부분의 운영 체제 통합은 활성화되기 전에 명시적인 사용자 승인(예: 특정 파일 형식의 기본 애플리케이션으로 하위 앱 선택)을 요구하므로 이 위험이 완화된다.5.6. 할당량 및 제한
호스트 운영 체제와 사용자의 애플리케이션 실행기가 잠재적으로 리소스를 소진하거나 악용되는 것을 방지하기 위해, 플랫폼은 다음 두 가지 제한을 적용한다:-
상위 애플리케이션당 설치된 하위 앱 50개의 엄격한 제한.
-
단일 권한 프롬프트당 설치할 수 있는 하위 앱 20개의 제한.
일괄 설치 호출이 플랫폼 제한을 초과하면, 전체 add()
호출이 "QuotaExceededError"
DOMException으로
거부된다.
6. 통합
6.1. 권한 정책
이 명세는 Window
객체의 subApps
속성에서 노출하는 메서드를 사용할 수 있는지를 제어하는 기능을 정의한다.
이 기능의 이름은 "sub-apps"이다.
이 기능의 기본 허용 목록은
'none'이다. 사용자 에이전트는 특정 출처에 대해
이를 'self'로 재정의할 수 있다(예: 사용자의 결정에 따라).