アイドル検出API

コミュニティグループ草案報告,

この版:
https://wicg.github.io/idle-detection/
課題トラッキング:
GitHub
編集者:
(Google LLC)
テストスイート:
https://wpt.fyi/results/idle-detection/

概要

本書では、システム全体のユーザー存在信号を観測するためのウェブプラットフォームAPIを定義します。

この文書のステータス

この仕様は Web Platform Incubator Community Group によって公開されました。 これはW3C標準ではなく、W3C標準化のトラックにも載っていません。 W3C Community Contributor License Agreement (CLA) に基づき、限定的なオプトアウトやその他の条件が適用されることにご注意ください。 W3Cコミュニティおよびビジネスグループ について詳しくは、こちらをご覧ください。

1. はじめに

このセクションは規範的ではありません。

既存の機能を利用することで、ページが現在ユーザーに表示されているかどうかを hidden プロパティや onvisibilitychange イベントを用いて判断できます。また、onmousemoveonkeypress など、ユーザーの入力によって発生する他のイベントを監視することで、ユーザーが最近ページとやり取りしたかどうかも知ることができます。これらのイベントは、特定のページに対するユーザーの関与を十分に反映しますが、ユーザーがまだデバイスの前にいるかどうかについては不完全な情報しか提供しません。例えば、hiddentrue の場合、デバイスのスクリーンセーバーが起動しているか、ユーザーが別のアプリケーションに切り替えた可能性があります。もし 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 要素に追加することで個別に有効化できます:

<iframe src="https://example.com" allow="idle-detection"></iframe>

また、HTTPレスポンスヘッダーで権限ポリシーを指定することで、ファーストパーティ環境で完全にこの機能を無効化できます:

Permissions-Policy: idle-detection 'none'

詳細は [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は以下の手順です:
  1. this.[[userState]] を返す。

2.4.2. screenState 属性

screenState のgetterは以下の手順です:
  1. this.[[screenState]] を返す。

2.4.3. onchange 属性

onchangeイベントハンドラーIDL属性であり、change イベントタイプに対応します。

2.4.4. requestPermission() メソッド

requestPermission() メソッドの手順は以下の通りです:
  1. もし this関連グローバルオブジェクト関連付けられたDocument完全にアクティブでなければ、拒否されたpromiseを "InvalidStateError" DOMException で返す。

    テスト
  2. this の関連グローバルオブジェクトが this一時的なアクティベーション を持たない場合、拒否されたpromiseを "NotAllowedError" DOMException で返す。

  3. result新しいpromise とする。

  4. 並行して

    1. permissionState"idle-detection" 権限の利用許可を要求した結果とする。

    2. グローバルタスクをキューイングし、関連グローバルオブジェクトアイドル検出タスクソース を使用して resolve resultpermissionState で解決する。

  5. result を返す。

2.4.5. start() メソッド

start(options) メソッドの手順は以下の通りです:

  1. documentthis関連グローバルオブジェクト関連付けられた Document とする。

  2. もし document完全にアクティブでなければ、拒否された promise を "InvalidStateError" DOMException で返す。

    テスト
  3. もし document"idle-detection" 権限 を 使用することが許可されていなければ、拒否された promise を "NotAllowedError" DOMException で返す。

    テスト
  4. もし this.[[state]]"stopped" でなければ、 拒否された promise を "InvalidStateError" DOMException で返す。

    テスト
  5. this.[[state]]"starting" に設定する。

  6. もし options["threshold"] が 60,000 未満なら、拒否された promiseTypeError で返す。

    テスト
  7. result新しい promise とする。

  8. もし options["signal"] が存在する場合、以下のサブステップを実行する:

    1. もし options["signal"] が aborted なら、resultoptions["signal"] の abort reason で reject し、result を返す。

    2. 以下の abort 手順を追加する:

      1. this.[[state]]"stopped" に設定する。

      2. resultoptions["signal"] の abort reason で reject する。

    テスト
  9. 並行して

    1. permissionState"idle-detection" 権限の状態とする。

      テスト
    2. グローバルタスクをキューイングし、関連グローバルオブジェクトアイドル検出タスクソース を使用して以下のステップを実行する:

      1. もし permissionState"denied" なら、

        1. this.[[state]]"stopped" に設定する。

        2. result を "NotAllowedError" DOMException で reject する。

      2. それ以外の場合、

        1. もし this.[[state]]"stopped" なら、これらの手順を中止する。

        2. this.[[state]]"started" に設定する。

        3. this.[[threshold]]options["threshold"] に設定する。

        4. result を resolve する。

  10. 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" イベントリスナーを追加すると userStatescreenState 属性が変化したときに発火します。

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);
}

後からページが状態変化イベントへの関心をキャンセルしたい場合は、イベントリスナーを削除するか、AbortSignalstart() に渡して利用します。

controller.abort();
console.log('IdleDetector is stopped.');

2.4.6. 状態変化への反応

IdleDetector インスタンス detectordetector.[[state]]"started" のもの)について、ユーザーエージェントは以下の条件を継続的に監視しなければなりません:

テスト

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 の皆様には本提案の作成に多大なご協力をいただき、心より感謝申し上げます。

適合性

文書規則

適合性要件は、記述的な宣言とRFC 2119の用語の組み合わせで表現されています。 規範部分における “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” というキーワードは RFC 2119 の定義通りに解釈されます。 ただし可読性の観点から、本仕様書内ではこれらの単語はすべて大文字ではありません。

明示的に非規範的と示されたセクション、例、および注記を除き、本仕様書の全テキストは規範的です。[RFC2119]

本仕様書の例は “for example” という語句で導入されるか、または class="example" のように規範テキストと区別されています。

これは情報提供目的の例です。

情報提供目的の注記は “Note” という語で始まり、class="note" のように規範テキストと区別されています。

注:これは情報提供目的の注記です。

テスト

本仕様の内容に関するテストはこのような “Tests” ブロックで文書化される場合があります。 これらのブロックはすべて非規範的です。


索引

本仕様で定義される用語

参照で定義される用語

参考文献

規範的参考文献

[DOM]
Anne van Kesteren. DOM Standard. Living Standard. URL: https://dom.spec.whatwg.org/
[ECMASCRIPT]
ECMAScript Language Specification. URL: https://tc39.es/ecma262/multipage/
[HTML]
Anne van Kesteren; et al. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[PAGE-VISIBILITY-2]
Ilya Grigorik; Marcos Caceres. Page Visibility Level 2. URL: https://w3c.github.io/page-visibility/
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

参考情報

[DEVICE-ORIENTATION]
Marcos Caceres; Reilly Grant. Device Orientation and Motion. URL: https://w3c.github.io/deviceorientation/
[FETCH]
Anne van Kesteren. Fetch Standard. Living Standard. URL: https://fetch.spec.whatwg.org/
[GEOLOCATION]
Marcos Caceres; Reilly Grant. Geolocation. URL: https://w3c.github.io/geolocation/
[PUSH-API]
Peter Beverloo; Martin Thomson; Marcos Caceres. Push API. URL: https://w3c.github.io/push-api/

IDL索引

enum UserIdleState {
    "active",
    "idle"
};

enum ScreenIdleState {
    "locked",
    "unlocked"
};

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 = {});
};

MDN

IdleDetector/IdleDetector

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

IdleDetector/change_event

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

IdleDetector/requestPermission_static

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

IdleDetector/screenState

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

IdleDetector/start

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

IdleDetector/userState

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

IdleDetector

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)NoneIENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?
MDN

Headers/Permissions-Policy/idle-detection

In only one current engine.

FirefoxNoneSafariNoneChrome94+
Opera?Edge94+
Edge (Legacy)?IENone
Firefox for Android?iOS Safari?Chrome for Android?Android WebView?Samsung Internet?Opera Mobile?