1. 소개
이 섹션은 규범적이지 않습니다.
기존 기능을 사용하면 페이지가 현재 사용자가 보고 있는지(
속성과 onvisibilitychange
이벤트를 통해) 확인할 수 있습니다. 또한 사용자가 최근에 페이지와 상호작용했는지 onmousemove,
onkeypress,
그리고
사용자 입력으로 인해 발생하는 기타 이벤트를 관찰하여 알 수 있습니다. 이러한 이벤트는 특정 페이지에 대한 사용자 몰입도를 충분히 반영하지만, 사용자가 여전히 자신의 디바이스 앞에 있는지에 대한
완전한 정보를 제공하지는 않습니다. 예를 들어
이 true라면, 디바이스의 화면 보호기가 활성화되었거나 사용자가 다른 애플리케이션으로 전환했을 수 있습니다. false이지만 최근 입력 이벤트가
없다면, 사용자가 커피를 가지러 자리를 비웠거나 다른 창에서 문서를 편집하고 있을 수도 있습니다.
이러한 구분은 데스크톱과 스마트폰 같이 여러 디바이스에서 알림을 전달할 수 있는 애플리케이션에 중요합니다. 알림이 잘못된 디바이스로 전달되거나 방해가 될 때 사용자는 불편을 겪을 수 있습니다. 예를 들어, 사용자가 메시징 애플리케이션이 있는 탭에서 문서 편집용 탭으로 전환하면, 메시징 애플리케이션은 사용자가 아직 디바이스와 상호작용 중임을 관찰하지 못하여 커피를 가지러 간 것으로 오인해서 폰으로 알림을 보내게 되고, 이로 인해 폰이 산만하게 진동하게 되거나, 데스크톱에 알림을 표시하거나 배지 카운트를 증가시키는 대신 폰으로 알림이 전달될 수 있습니다.
1.1. 고려된 대안
대안 설계로는 알림을 "활성 시 숨김" 또는 "유휴 시 숨김"으로 표시하고 페이지에서 알림이 실제로 표시되었는지 관찰하지 못하게 하는 방식이 있습니다. 하지만 앞서 설명한 지능적인 알림 라우팅을 구현하려면 사용자 존재 신호를 관찰하고 모든 디바이스 상태를 바탕으로 중앙에서 결정해야 합니다.
예를 들어, 메시징 애플리케이션이 사용자가 커피를 가지러 간 것을 감지하면 더 이상 화면에 보이지 않음을 인식하여 모바일 디바이스로 푸시 메시지를 보내고 데스크톱 알림을 "활성 시 숨김"으로 표시할 수 있습니다. 사용자가 여전히 책상에 있지만 다른 애플리케이션을 사용 중이라면, 데스크톱이 성공적으로 알림을 억제하더라도 모바일 디바이스에서 산만한 알림을 받게 되어 이 제안이 방지하려는 상황이 발생합니다. 중복 및 방해가 되는 알림의 성공적인 억제를 위해서는 여러 디바이스 간의 조정이 필요합니다.
알림 숨기기를 허용하면 [PUSH-API]를 사용한 무음 백그라운드 작업에 대한 구현자 대응책도 깨질 수 있습니다.
2. 사용자 존재 관찰
2.1. 모델
이 명세는 사용자 존재에 대해 두 가지 차원(유휴 상태와 화면 잠금)에 대한 모델을 정의합니다.
2.1.1. UserIdleState
열거형
enum {UserIdleState ,"active" };"idle"
"active"-
사용자가 마지막
threshold밀리초 내에 디바이스와 상호작용했음을 나타냅니다. "idle"-
사용자가 최소
threshold밀리초 동안 디바이스와 상호작용하지 않았음을 나타냅니다.
2.1.2. ScreenIdleState
열거형
enum {ScreenIdleState ,"locked" };"unlocked"
"locked"-
디바이스가 화면 보호기 또는 잠금 화면을 활성화하여 콘텐츠를 볼 수 없거나 상호작용할 수 없음을 나타냅니다.
"unlocked"-
디바이스가 콘텐츠를 표시하고 상호작용할 수 있음을 나타냅니다.
2.2. 권한
"idle-detection" 권한은 기본 강력 기능입니다.
2.3. 권한 정책
이 명세는 정책 제어 기능을 "idle-detection" 문자열로 식별합니다. 기본 허용 목록은 'self'입니다.
'self'의 기본 허용 목록은 동일 출처의 중첩 프레임에서 기본적으로 이 기능을 사용할
수 있게 하지만, 서드파티 콘텐츠의 접근을 방지합니다.
서드파티 사용은 allow="idle-detection" 속성을 iframe
요소에 추가하여 선택적으로 활성화할 수 있습니다:
또는, HTTP 응답 헤더에서 권한 정책을 지정하여 퍼스트파티 컨텍스트에서 이 기능을 완전히 비활성화할 수 있습니다:
자세한 내용은 [PERMISSIONS-POLICY]를 참고하세요.
2.4. IdleDetector
인터페이스
dictionary { [IdleOptions EnforceRange ]unsigned long long ;threshold AbortSignal ; }; [signal SecureContext ,Exposed =(Window ,DedicatedWorker ) ]interface :IdleDetector EventTarget {();constructor readonly attribute UserIdleState ?userState ;readonly attribute ScreenIdleState ?screenState ;attribute EventHandler onchange ; [Exposed =Window ]static Promise <PermissionState >requestPermission ();Promise <undefined >start (optional IdleOptions = {}); };options
IdleDetector
인스턴스는 아래 표에서 설명하는 내부 슬롯과 함께 생성됩니다:
| 내부 슬롯 | 초기 값 | 설명(비규범적) |
|---|---|---|
[[state]]
| "stopped"
| IdleDetector
의 활성 상태 추적
|
[[threshold]]
| undefined
| 설정된 유휴 감지 임계값 |
[[userState]]
| null
| 마지막으로 알려진 사용자 유휴 상태 |
[[screenState]]
| null
| 마지막으로 알려진 화면 유휴 상태 |
이 인터페이스의 메서드는 일반적으로 비동기적으로 완료되며, idle detection task source에 작업을 큐에 등록합니다.
2.4.1.
userState
속성
userState getter 단계는 다음과 같습니다:
-
this.
[[userState]]를 반환합니다.
2.4.2.
screenState
속성
screenState getter 단계는 다음과 같습니다:
-
this.
[[screenState]]를 반환합니다.
2.4.3.
onchange
속성
onchange는 이벤트 핸들러 IDL
속성이며 change
이벤트 타입을 위한 것입니다.
2.4.4. requestPermission()
메서드
requestPermission() 메서드 단계는 다음과 같습니다:
-
this의 관련 글로벌 객체의 연결된 문서가 완전히 활성화되어 있지 않다면, 거부된 promise를 "
InvalidStateError"DOMException와 함께 반환합니다. -
this의 관련 글로벌 객체가 일시적 활성화가 없다면, 거부된 promise를 "
NotAllowedError"DOMException와 함께 반환합니다. -
result를 새로운 promise로 정의합니다.
-
병렬로:
-
permissionState를 사용 권한 요청의 결과로 정의합니다. "idle-detection"을 사용합니다.
-
글로벌 작업 큐를 this의 idle detection task source에 등록하여 resolve result를 permissionState로 처리합니다.
-
-
result를 반환합니다.
2.4.5.
start()
메서드
start(options) 메서드 단계는 다음과 같습니다:
-
document를 this의 관련 글로벌 객체의 연결된 Document로 설정합니다.
-
document가 완전히 활성화되어 있지 않다면, 거부된 promise를 "
InvalidStateError"DOMException와 함께 반환합니다. -
document가 allowed to use "idle-detection"이 아니라면, 거부된 promise를 "
NotAllowedError"DOMException와 함께 반환합니다.테스트
- idle-detection-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html (실시간 테스트) (소스)
- idle-detection-allowed-by-permissions-policy-attribute.https.sub.html (실시간 테스트) (소스)
- idle-detection-allowed-by-permissions-policy.https.sub.html (실시간 테스트) (소스)
- idle-detection-default-permissions-policy.https.sub.html (실시간 테스트) (소스)
- idle-detection-disabled-by-permissions-policy.https.sub.html (실시간 테스트) (소스)
-
this.
[[state]]이"stopped"가 아니라면, 거부된 promise를 "InvalidStateError"DOMException와 함께 반환합니다. -
this.
[[state]]를"starting"으로 설정합니다. -
options[
"threshold"] 가 60,000보다 작으면, 거부된 promise를TypeError와 함께 반환합니다. -
result를 새 promise로 설정합니다.
-
options["
signal"] 이 존재한다면, 다음 하위 단계 수행:-
options["
signal"] 이 aborted라면, reject result를 options["signal"]의 abort reason으로 거부하고 result를 반환합니다. -
다음 abort 단계 추가를 options["
signal"]에 수행:-
this.
[[state]]를"stopped"로 설정합니다. -
reject result를 options["
signal"]의 abort reason으로 거부합니다.
-
-
-
병렬로:
-
permissionState를 permission state의
"idle-detection"값으로 설정합니다. -
글로벌 작업 큐를 this의 관련 글로벌 객체에서 idle detection task source로 수행:
-
permissionState가
"denied"라면,-
this.
[[state]]를"stopped"로 설정합니다. -
reject result를 "
NotAllowedError"DOMException으로 거부합니다.
-
-
그 외의 경우,
-
this.
[[state]]가"stopped"라면, 이 단계를 중단합니다. -
this.
[[state]]를"started"로 설정합니다. -
this.
[[threshold]]를 options["threshold"]로 설정합니다. -
resolve result를 수행합니다.
-
-
-
-
result를 반환합니다.
참고: 위 단계는 병렬로 실행되어 구현체가 권한 상태를 비동기적으로 확인할 수 있도록 합니다.
이 API의 사용 가능 여부는 IdleDetector
생성자가 Window
객체에 있는지 확인하여 감지할 수 있습니다.
if ( ! ( 'IdleDetector' in window)) { console. log( 'Idle detection is not available.' ); return ; }
start()
호출은 "idle-detection" 권한이
허용되지 않은 경우 실패합니다.
if (( await IdleDetector. requestPermission()) !== 'granted' ) { console. log( 'Idle detection permission not granted.' ); return ; }
옵션 집합을 구성하여 user agent가 사용자의 유휴 여부를 판단할 때 사용할 임계값을 제어할 수 있습니다.
const controller= new AbortController(); const signal= controller. signal; const options= { threshold: 60 _000, signal, };
IdleDetector
를 생성하고 시작할 수 있습니다. "change"
이벤트에 대한 리스너를 추가하면 userState
또는 screenState
속성이 변경될 때 이벤트가 발생합니다.
try { const idleDetector= new IdleDetector(); idleDetector. addEventListener( 'change' , () => { console. log( `Idle change: ${ idleDetector. userState} , ${ idleDetector. screenState} .` ); }); await idleDetector. start( options); console. log( 'IdleDetector is active.' ); } catch ( err) { // 권한 거부, 최상위 프레임 외부 실행 등 초기화 오류 처리 console. error( err. name, err. message); }
나중에 페이지가 상태 변경 이벤트에 대한 관심을 취소하려면 이벤트 리스너를 제거하거나 AbortSignal
을 start()
에 전달하여 사용할 수 있습니다.
controller. abort(); console. log( 'IdleDetector is stopped.' );
2.4.6. 상태 변화에 반응하기
각 IdleDetector
인스턴스 detector에 대해 detector.[[state]]
가 "started"일 때 user agent는 다음 조건을 지속적으로 모니터링해야 합니다:
-
detector.
[[userState]]가"active"이고 사용자가 마지막 detector.[[threshold]]밀리초 내에 디바이스와 상호작용하지 않았다면, 글로벌 작업 큐를 detector의 관련 글로벌 객체에서 idle detection task source로 다음 단계를 실행:-
detector.
[[userState]]를"idle"로 설정합니다. -
"change" 이벤트를 detector에서 발생시킵니다.
-
-
detector.
[[userState]]가"idle"이고 사용자가 디바이스와 상호작용하면, 글로벌 작업 큐를 detector의 관련 글로벌 객체에서 idle detection task source로 다음 단계를 실행:-
detector.
[[userState]]를"active"로 설정합니다. -
"change" 이벤트를 detector에서 발생시킵니다.
-
-
detector.
[[screenState]]가"unlocked"이고 화면이 잠기면, 글로벌 작업 큐를 detector의 관련 글로벌 객체에서 idle detection task source로 다음 단계 실행:-
detector.
[[screenState]]를"locked"로 설정합니다. -
"change" 이벤트를 detector에서 발생시킵니다.
-
-
detector.
[[screenState]]가"locked"이고 화면이 잠금 해제되면, 글로벌 작업 큐를 detector의 관련 글로벌 객체에서 idle detection task source로 다음 단계 실행:-
detector.
[[screenState]]를"unlocked"로 설정합니다. -
"change" 이벤트를 detector에서 발생시킵니다.
-
3. 보안 및 개인정보 보호 고려사항
이 섹션은 규범적이지 않습니다.
3.1. 교차 출처 정보 누출
이 인터페이스는 글로벌 시스템 속성의 상태를 노출하므로, 교차 출처 통신 또는 식별 채널로 악용되지 않도록 주의해야 합니다. 유사한 문제는 [DEVICE-ORIENTATION] 및 [GEOLOCATION]와 같은 명세에서도 존재하며, 이들은 가시성 또는 포커스가 있는 컨텍스트를 요구함으로써 이를 완화합니다. 이는 여러 출처가 동시에 글로벌 상태를 관찰하는 것을 방지합니다. 그러나 이 명세의 목적은 흐릿하거나 숨겨진 컨텍스트에서의 제한적 추적을 허용하는 것이므로 이러한 완화책은 적합하지 않습니다. 악의적인 페이지는 사용자가 유휴 또는 활성 상태가 될 때마다 추적 서버에 알릴 수 있습니다. 여러 페이지가 동일한 서버에 알린다면 이벤트의 타이밍을 통해 어떤 세션이 하나의 사용자에 대응하는지 추정할 수 있습니다. 이벤트가 거의 동시에 도착하기 때문입니다.
이 인터페이스에 접근할 수 있는 독립적인 컨텍스트의 수를 줄이기 위해, 이 명세는 최상위 및 동일 출처 컨텍스트로 제한합니다. [PERMISSIONS-POLICY]를 통해 교차 출처 컨텍스트에 접근을 위임할 수 있습니다.
더 나아가 이 명세는 페이지가 "idle-detection" 권한을 획득하도록 요구합니다. 사용자 에이전트는 이 권한이 부여하는 기능에 대해 사용자를 안내하고, 이 데이터가 정당한 목적을 지닌 신뢰할 수 있는 사이트에만 허용하도록 권장해야 합니다.
"프라이빗 브라우징" 모드를 제공하는 구현체는 해당 모드가 활성화된 컨텍스트에서는 이 기능을 허용하지 않아야 합니다. 그러나 이 기능의 부재가 해당 모드가 활성화되었음을 나타내는 신호로 사용되지 않도록 주의해야 합니다. 이를 위해 "idle-detection" 권한이 허용되지 않도록 하면서, 권한 요청의 자동 거부를 무작위 지연하여 사용자 조작으로 보이게 할 수 있습니다.
3.2. 행동 추적
이 인터페이스는 "idle"
에서 "active"
로 전환을 유발한 사용자 상호작용의 세부 정보를 제공하지 않지만, 임계값이 충분히 짧다면 이러한 이벤트로 타이핑 등의 행동을 감지할 수 있습니다. 따라서 이 명세는 요청 가능한 최소 임계값을
60초 이상으로 제한합니다.
앞서 설명한 권한 요구는 이 인터페이스가 사용자의 디바이스 상호작용 시간 및 빈도에 대한 프로파일을 구축하는 데 사용될 수 있다는 일반적인 우려를 완화하는 데도 도움이 됩니다.
3.3. 사용자 강요
사이트는 일부 기능을 해제하기 전에 사용자가 허용하도록 "idle-detection" 권한을 요구할 수 있습니다. 예를 들어, 테스트 사이트는 사용자가 다른 창에서 금지된 참고 자료를 보는지 감지하기 위한 부정행위 방지 메커니즘의 일환으로 이 권한을 요구할 수 있습니다. 이러한 "부착 계약"은 알림, FIDO 인증 및 DRM 식별자와 같은 다른 권한에서도 관찰되었습니다.
이 문제에 대한 잠재적 완화책으로, 사이트가 사용자가 권한을 허용했는지 또는 거부했는지 판단할 수 없도록 인터페이스를 설계할 수 있습니다. 구현체는 사용자가 유휴 상태인지 인지하지 않도록 할 수 있으며, 사이트는 현재 제공되는 신호만 사용할 수 있게 됩니다. 이 완화책은 사용자가 오랜 시간 동안 페이지와 상호작용하지 않았음에도 불구하고 다른 곳에서 계속 상호작용하고 있다고 판단하는 것은 드물기 때문에 사이트에서 감지될 수 있습니다. 대신, 구현체는 페이지에 제공되는 다른 신호를 바탕으로 실제와 근접한 가짜 유휴 전환 이벤트를 삽입할 수 있습니다.
이 명세는 이러한 완화책을 필수로 요구하지 않습니다. 이는 사이트가 잘못된 데이터에 기반해 행동할 경우 사용자 경험을 저하시킬 수 있기 때문입니다. 예를 들어, 앞서 언급한 메시징 애플리케이션은 사용자가 여전히 데스크톱에 있다고 잘못 인식하여 모바일 디바이스로 알림을 전달하지 않을 수 있습니다. 사이트가 이러한 상태임을 감지할 수 없으므로 사용자가 이를 벗어나기 위한 직접적인 행동을 안내할 수 없습니다.
이러한 사이트로 인한 피해는 사용자가 해당 페이지를 방문하는 동안에만 추적이 가능하므로 제한적입니다. 여러 출처에 걸친 추적은 각 참여 사이트에서 별도의 권한 요청이 필요합니다.
4. 접근성 고려사항
이 섹션은 규범적이지 않습니다.
신체적 또는 인지적 장애가 있는 사용자는 사용자 에이전트 및 콘텐츠와 상호작용하는 데 더 많은 시간이 필요할 수 있습니다. 구현체는 이러한 사용자를 구분하거나 기존 UI 이벤트 관찰보다 상호작용 능력을 더 제한해서는 안 됩니다. 예를 들어, 구현은 보조 기술에서 발생하는 상호작용도 사용자가 활성 상태로 간주하는 데 포함해야 합니다.
권한 사용은 또한 사용자 에이전트가 해당 권한을 요청 및 관리할 수 있도록 사용자 인터페이스 요소를 제공할 것을 요구합니다. 모든 사용자 인터페이스 요소는 접근성 도구를 염두에 두고 설계되어야 합니다. 예를 들어, 요청되는 기능을 설명하는 사용자 인터페이스는 화면 읽기 도구 등에도 동일한 설명을 제공해야 합니다.
5. 국제화 고려사항
이 섹션은 규범적이지 않습니다.
이 명세에서 설명하는 인터페이스는 국제화 고려사항이 제한적이지만, 권한 사용은 사용자 에이전트가 권한 요청 및 관리를 지원하는 사용자 인터페이스 요소를 제공할 것을 요구합니다. 이 컨텍스트에서 사용자 에이전트가 표시하는 모든 콘텐츠는 사용자의 모국어로 번역되어야 합니다.
6. 감사의 글
이 섹션은 규범적이지 않습니다.
Kenneth Christiansen, Samuel Goto, Ayu Ishii, Thomas Steiner 등 이 제안 작성에 도움을 준 모든 분들께 감사드립니다.