1. はじめに
このセクションは規範的ではありません。
既存の機能を利用することで、ページが現在ユーザーに表示されているかどうかを
プロパティや onvisibilitychange
イベントを用いて判断できます。また、onmousemove、
onkeypress
など、ユーザーの入力によって発生する他のイベントを監視することで、ユーザーが最近ページとやり取りしたかどうかも知ることができます。これらのイベントは、特定のページに対するユーザーの関与を十分に反映しますが、ユーザーがまだデバイスの前にいるかどうかについては不完全な情報しか提供しません。例えば、
が true の場合、デバイスのスクリーンセーバーが起動しているか、ユーザーが別のアプリケーションに切り替えた可能性があります。もし false
でも最近入力イベントがなければ、ユーザーはコーヒーを取りに席を離れているか、またはページと並行して別のウィンドウでドキュメントを編集しているかもしれません。
これらの区別は、デスクトップやスマートフォンなど複数のデバイスで通知を配信するアプリケーションにとって重要です。通知が誤ったデバイスに届いたり、邪魔になったりすると、ユーザーにとって不快です。例えば、メッセージングアプリのタブからドキュメント編集用のタブに切り替えた場合、メッセージングアプリはユーザーがデバイスを離れたと誤認して、通知をスマートフォンに送り始めてしまい、スマートフォンが煩わしく鳴動したり、デスクトップ上で通知が表示されずバッジカウントが増えるだけになったりします。
1.1. 検討された代替案
代替設計としては、通知を「アクティブ時に非表示」や「アイドル時に非表示」としてマークし、実際に通知が表示されたかどうかをページ側から観測できなくする方法も考えられます。しかしこの方法では、先述した知的な通知ルーティングを実現するにはユーザーの存在信号を観測し、すべてのデバイスの状態をもとに集中管理の判断を行う必要があるため、適しません。
例えば、ユーザーが席を離れてコーヒーを取りに行った場合、メッセージングアプリは自分が表示されていないことを検知し、モバイルデバイスにプッシュ通知を送り始め、デスクトップ側の通知は「アクティブ時に非表示」とマークします。ユーザーがまだデスクにいるが別のアプリケーションを使っている場合、この提案が防ごうとしている煩わしいモバイル通知が届いてしまい、デスクトップ側がそれらを抑制できるかどうかにかかわらず、通知の重複や妨害を防ぐにはマルチデバイス間の協調が必要です。
通知を非表示にすることを許可すると、[PUSH-API] を使ったサイレントなバックグラウンド処理への実装者による対応策も破綻します。
2. ユーザー存在の観測
2.1. モデル
この仕様では、ユーザーの存在を「アイドル状態」と「画面ロック」という2つの側面でモデル化します。
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
| 直近の画面アイドル状態 |
このインターフェースのメソッドは通常非同期で完了し、アイドル検出タスクソースで作業をキューイングします。
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 の 関連グローバルオブジェクト の 関連付けられたDocument が 完全にアクティブでなければ、拒否されたpromiseを "
InvalidStateError"DOMExceptionで返す。 -
this の関連グローバルオブジェクトが this の 一時的なアクティベーション を持たない場合、拒否されたpromiseを "
NotAllowedError"DOMExceptionで返す。 -
result を 新しいpromise とする。
-
並行して:
-
permissionState を "idle-detection" 権限の利用許可を要求した結果とする。
-
グローバルタスクをキューイングし、関連グローバルオブジェクトで アイドル検出タスクソース を使用して resolve result を permissionState で解決する。
-
-
result を返す。
2.4.5.
start()
メソッド
start(options) メソッドの手順は以下の通りです:
-
document を this の 関連グローバルオブジェクト の 関連付けられた Document とする。
-
もし document が 完全にアクティブでなければ、拒否された promise を "
InvalidStateError"DOMExceptionで返す。 -
もし document が "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 なら、result を options["signal"] の abort reason で reject し、result を返す。 -
-
this.
[[state]]を"stopped"に設定する。 -
result を options["
signal"] の abort reason で reject する。
-
-
-
並行して:
-
permissionState を "idle-detection" 権限の状態とする。
-
グローバルタスクをキューイングし、関連グローバルオブジェクトで アイドル検出タスクソース を使用して以下のステップを実行する:
-
もし permissionState が
"denied"なら、-
this.
[[state]]を"stopped"に設定する。 -
result を "
NotAllowedError"DOMExceptionで reject する。
-
-
それ以外の場合、
-
もし this.
[[state]]が"stopped"なら、これらの手順を中止する。 -
this.
[[state]]を"started"に設定する。 -
this.
[[threshold]]を options["threshold"] に設定する。 -
result を resolve する。
-
-
-
-
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 ; }
ユーザーがアイドルになったと判定するしきい値は、オプションで設定できます。
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" のもの)について、ユーザーエージェントは以下の条件を継続的に監視しなければなりません:
-
もし detector.
[[userState]]が"active"であり、 ユーザーが detector.[[threshold]]ミリ秒間デバイスを操作していなければ、グローバルタスクをキューイングし、関連グローバル オブジェクトで アイドル検出タスクソース を使用して以下の手順を実行する:-
detector.
[[userState]]を"idle"に設定する。 -
"change" イベントを detector で発火する。
-
-
もし detector.
[[userState]]が"idle"であり、 ユーザーがデバイスを操作した場合、グローバルタスクをキューイングし、関連グローバル オブジェクトで アイドル検出タスクソース を使用して以下の手順を実行する:-
detector.
[[userState]]を"active"に設定する。 -
"change" イベントを detector で発火する。
-
-
もし detector.
[[screenState]]が"unlocked"であり、 画面がロックされた場合、グローバルタスクをキューイングし、関連グローバルオブジェクトで アイドル検出タスクソース を使用して以下の手順を実行する:-
detector.
[[screenState]]を"locked"に設定する。 -
"change" イベントを detector で発火する。
-
-
もし detector.
[[screenState]]が"locked"であり、 画面がアンロックされた場合、グローバルタスクをキューイングし、関連グローバルオブジェクトで アイドル検出タスクソース を使用して以下の手順を実行する:-
detector.
[[screenState]]を"unlocked"に設定する。 -
"change" イベントを detector で発火する。
-
3. セキュリティおよびプライバシーの考慮事項
このセクションは規範的ではありません。
3.1. クロスオリジン情報漏洩
このインターフェースは、グローバルなシステムプロパティの状態を公開するため、クロスオリジン通信や識別チャンネルとして悪用されないよう注意が必要です。類似の懸念は [DEVICE-ORIENTATION] や [GEOLOCATION] などの仕様にもあり、可視またはフォーカスされたコンテキストを要求することで対策しています。これにより複数のオリジンが同時にグローバルな状態を観測できなくなります。しかし本仕様の目的は、ぼやけた状態や非表示のコンテキストでも限定的に追跡を許可することなので、これらの対策は適切ではありません。悪意のあるページがユーザーがアイドルかアクティブかを検出するたびに追跡サーバーへ通知する可能性があります。ユーザーが閲覧している複数ページが同じサーバーに通知を送信した場合、イベントのタイミングから複数セッションが同一ユーザーであることを推測される恐れがあります。
このインターフェースへのアクセスを独立したコンテキスト数を減らすため、本仕様はトップレベルおよび同一オリジンのコンテキストに限定しています。アクセスは [PERMISSIONS-POLICY] を通じてクロスオリジンコンテキストに委譲できます。
さらにコンテキスト数を減らすため、本仕様ではページが "idle-detection" 権限を取得する必要があります。ユーザーエージェントは、この権限が付与する機能についてユーザーに説明し、信頼できる正当な目的を持つサイトにのみ 許可するよう促すべきです。
「プライベートブラウジング」モードを提供する実装では、この機能をモード有効時のコンテキストで利用できないようにすべきです。ただし、この機能が利用できないこと自体がモード有効のシグナルとならないよう注意が必要です。これを防ぐには、"idle-detection" 権限の 許可を拒否しつつ、許可要求の自動却下をランダムな間隔で遅延させることでユーザー操作に見せかけることができます。
3.2. 行動追跡
このインターフェースは "idle"
から "active"
への遷移を引き起こしたユーザー操作の詳細は提供しませんが、しきい値が十分に短い場合、例えばタイピングなどの行動を検出するために利用される可能性があります。そのため、本仕様ではしきい値の最小値を60秒以上に制限しています。
前述の権限要件も、このインターフェースがユーザーのデバイス操作の時間や頻度等のプロファイルを構築する目的で利用されることへの一般的な懸念の緩和に役立ちます。
3.3. ユーザーの強要
サイトが、何らかの機能を解放する前にユーザーに 権限を与えるよう要求する場合があります。例えば、テストサイトが不正行為防止の仕組みとして他ウィンドウで禁止された参考資料を参照していないか検出するためこの権限を要求することがあります。このような「付和契約」は通知、FIDO認証、DRM識別子など他の権限でも観察されています。
この懸念への対策として、ユーザーが権限を 許可したか 拒否したかサイトが判別できない設計が考えられます。実装はユーザーがアイドルであることを認識しないようにして、サイト側が既存の信号のみを利用できるように制限することもできます。この対策は、何時間も操作していないのに別の何かを継続的に操作していたと考えるのは非現実的なので検出可能となる場合があります。実装は他の信号と整合するような偽のアイドル遷移イベントを挿入することもできます。
この仕様では、この種の緩和策は義務付けていません。なぜなら、偽データに基づいてサイトが動作するとユーザー体験が悪化する可能性があるためです。例えば、前述のメッセージアプリが誤った信号を信じてユーザーがデスクトップにいると判断し、モバイルデバイスへの通知を行わなくなります。サイトは自分がこの状態にあることを検知できないため、ユーザーが状況を回避するための直接的な推奨もできません。
このようなサイトによる害は、ユーザーがそのページを閲覧している間だけ追跡が可能なため限定的です。複数オリジンにまたがる追跡には、それぞれの参加サイトで権限要求が必要となります。
4. アクセシビリティの考慮事項
このセクションは規範的ではありません。
身体的または認知的障害を持つユーザーは、ユーザーエージェントやコンテンツとのやりとりにより長い時間を要する場合があります。実装は、既存のUIイベントの観測以上にこのようなユーザーの区別やコンテンツとのやりとりの制限を許可すべきではありません。例えば、支援技術による操作もユーザーがアクティブとみなされるよう保証すべきです。
権限の利用には、ユーザーエージェントが権限の要求や管理をサポートするユーザーインターフェース要素を提供することも求められます。これらのUI要素は、アクセシビリティツールへの配慮をもって設計されなければなりません。例えば、要求される機能を説明するUIはスクリーンリーダー等のツールにも同じ説明を提供すべきです。
5. 国際化の考慮事項
このセクションは規範的ではありません。
本仕様で説明されるインターフェースは国際化への考慮は限定的ですが、権限の利用にはユーザーエージェントが権限要求や管理をサポートするユーザーインターフェース要素を提供する必要があります。このような状況でユーザーエージェントが表示する内容は、ユーザーの母国語に翻訳すべきです。
6. 謝辞
このセクションは規範的ではありません。
Kenneth Christiansen, Samuel Goto, Ayu Ishii, Thomas Steiner の皆様には本提案の作成に多大なご協力をいただき、心より感謝申し上げます。