# 通知設計書 21-ScrollViewport

## 概要

本ドキュメントは、Windows Terminalのコンソールホスト（conhost）におけるScrollViewportアクセシビリティ通知の設計を記述する。ビューポート（表示領域）のスクロール操作が行われた際に、MSAA（Microsoft Active Accessibility）経由でスクリーンリーダー等の支援技術にスクロール情報を通知する仕組みである。

### 本通知の処理概要

ScrollViewport通知は、コンソールウィンドウのビューポートがスクロールされた際に、支援技術（スクリーンリーダー等）に対してスクロール変位を伝達するアクセシビリティ通知である。

**業務上の目的・背景**：視覚障がいのあるユーザーがスクリーンリーダーを使用してコンソールを操作する際、ビューポートのスクロール状態を把握する必要がある。スクロールが発生したことを支援技術に通知しないと、ユーザーは画面上の表示位置の変化を認識できない。本通知は、EVENT_CONSOLE_UPDATE_SCROLLイベントを通じて、スクロールの方向と量を支援技術に伝達することで、この課題を解決する。

**通知の送信タイミング**：ウィンドウのビューポートスクロール操作が行われた際に送信される。具体的には、`Window::ChangeViewport`メソッドで新しいビューポート位置が設定される直前に、旧ビューポート位置との差分（delta）が計算され、AccessibilityNotifier::ScrollViewportが呼び出される。

**通知の受信者**：WinEventフック（SetWinEventHookでEVENT_CONSOLE_UPDATE_SCROLLを購読しているプロセス）を通じて、スクリーンリーダー（NVDA、Narrator等）やその他の支援技術が受信する。

**通知内容の概要**：水平方向（deltaX）と垂直方向（deltaY）のスクロール変位量がEVENT_CONSOLE_UPDATE_SCROLLイベントのパラメータとして送信される。

**期待されるアクション**：支援技術はスクロール情報を受け取り、ユーザーに対して音声やその他のフィードバックで現在のスクロール位置の変化を伝達する。

## 通知種別

アクセシビリティ通知（MSAA WinEvent: EVENT_CONSOLE_UPDATE_SCROLL）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 非同期（デバウンスタイマーによるバッチ処理）、ただしdelay=0の場合は同期 |
| 優先度 | 中 |
| リトライ | 無（NotifyWinEvent APIに委譲） |

### 送信先決定ロジック

WinEventフック機構を使用する。`IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_SCROLL)`がtrueの場合のみ通知を発行する。受信先はSetWinEventHookでEVENT_CONSOLE_UPDATE_SCROLLを購読している全プロセスとなる。

## 通知テンプレート

### WinEventパラメータ

| 項目 | 内容 |
|-----|------|
| イベントID | EVENT_CONSOLE_UPDATE_SCROLL |
| HWND | コンソールウィンドウハンドル（_hwnd） |
| idObject (LONG) | 水平スクロール変位（deltaX） |
| idChild (LONG) | 垂直スクロール変位（deltaY） |

### 本文テンプレート

```
NotifyWinEvent(EVENT_CONSOLE_UPDATE_SCROLL, _hwnd, dx, dy)
```

dx: 水平方向のスクロール変位（列数）
dy: 垂直方向のスクロール変位（行数）

### 添付ファイル

該当なし（WinEventベースの通知のため）

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| delta.x | 水平方向スクロール変位 | ビューポート位置の差分計算 | Yes |
| delta.y | 垂直方向スクロール変位 | ビューポート位置の差分計算 | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| 画面操作 | ビューポートスクロール（Window::ChangeViewport） | MSAAが有効 かつ IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_SCROLL)がtrue | ユーザーのスクロールバー操作、キーボード操作等によるビューポート移動時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| MSAAが無効 | _msaaEnabled が false の場合（msaaDelay >= 10000 または HWND が null） |
| WinEventフック未登録 | IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_SCROLL) が false の場合 |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[ビューポートスクロール操作] --> B[Window::ChangeViewport呼び出し]
    B --> C[旧ビューポート位置と新ビューポート位置の差分計算]
    C --> D[AccessibilityNotifier::ScrollViewport呼び出し]
    D --> E{MSAAが有効 かつ WinEventフック登録済み?}
    E -->|Yes| F[排他ロック取得]
    E -->|No| G[処理スキップ]
    F --> H[State構造体にdeltaX/deltaYを蓄積]
    H --> I[_timerSet呼び出し]
    I --> J{delayが0?}
    J -->|Yes| K[即座に_emitEvents実行]
    J -->|No| L[タイマースケジュール]
    L --> M[タイマー発火時に_timerEmitMSAA実行]
    K --> N[NotifyWinEvent EVENT_CONSOLE_UPDATE_SCROLL発行]
    M --> N
    N --> O[終了]
    G --> O
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（データベースを使用しない。メモリ内のState構造体のみ）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| NotifyWinEvent失敗 | OSレベルの障害 | NotifyWinEventはvoid戻り値のため、エラーは黙殺される |
| ロック競合 | 排他ロック取得時のデッドロック | SRWLockを使用しているため、デッドロックリスクは低い |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| デバウンス遅延 | msaaDelay設定値（レジストリから取得、デフォルトは実装依存） |
| デバウンスウィンドウ | 遅延の1/5 |

### 配信時間帯

制限なし（コンソール稼働中は常時有効）

## セキュリティ考慮事項

- WinEventはOSレベルのメカニズムであり、イベントを受信できるのはSetWinEventHookを呼び出したプロセスに限られる
- スクロール変位情報のみを送信し、画面内容自体は含まれない
- 個人情報・機密情報の漏洩リスクは極めて低い

## 備考

- ScrollViewportはビューポートスクロール専用であり、バッファスクロール（ScrollBuffer）とは別の関数として実装されている
- EVENT_CONSOLE_UPDATE_SCROLLはビューポートスクロールとバッファスクロールの両方に使用される同一イベントIDである（コメントでは「情報が完全に無意味になる」と嘆かれている）
- ScrollViewportではUIAイベントは発行されない（MSAAのみ）

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

AccessibilityNotifierのState構造体でスクロール変位がどのように管理されるかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | AccessibilityNotifier.h | `src/host/AccessibilityNotifier.h` | State構造体のeventConsoleUpdateScrollDeltaX/Y、eventConsoleUpdateScrollPrimedフィールド（行57-59） |
| 1-2 | AccessibilityNotifier.h | `src/host/AccessibilityNotifier.h` | クラス全体のメンバー変数、特に_msaaEnabled、_delay、_timer（行78-98） |

**読解のコツ**: State構造体はmemsetで0初期化される前提で設計されている。各イベント種別にPrimedフラグがあり、trueになって初めてそのイベントが発行される。

#### Step 2: エントリーポイントを理解する

ビューポートスクロール時にScrollViewportがどのように呼び出されるかを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | window.cpp | `src/interactivity/win32/window.cpp` | ChangeViewport関数内でScrollViewportが呼び出される箇所（行428-429） |

**主要処理フロー**:
1. **行428**: 旧ビューポート位置と新ビューポート位置の差分計算（`ScreenInfo.GetViewport().Left() - NewWindow.left` 等）
2. **行429**: `an.ScrollViewport({差分})` の呼び出し

#### Step 3: ScrollViewport関数の実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | ScrollViewport関数（行277-289）。MSAAが有効かつフック登録済みの場合のみState構造体に差分を蓄積 |

**主要処理フロー**:
- **行279**: `_msaaEnabled && IsWinEventHookInstalled(EVENT_CONSOLE_UPDATE_SCROLL)` の条件チェック
- **行283-285**: State構造体にdeltaX/deltaYを加算し、Primedフラグをtrue設定
- **行287**: `_timerSet()` でタイマーのスケジュールまたは即時発行

#### Step 4: イベント発行処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | _timerSet関数（行329-340）。delay=0なら即時、そうでなければタイマー設定 |
| 4-2 | AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | _emitEvents関数（行526-536）。eventConsoleUpdateScrollPrimedがtrueの場合にNotifyWinEventを発行 |

**主要処理フロー**:
- **行528-529**: deltaX/deltaYをLONG型に飽和変換
- **行531**: `cc->NotifyWinEvent(EVENT_CONSOLE_UPDATE_SCROLL, _hwnd, dx, dy)` でWinEvent発行

### プログラム呼び出し階層図

```
Window::ChangeViewport (window.cpp:428)
    |
    +-- AccessibilityNotifier::ScrollViewport (AccessibilityNotifier.cpp:277)
           |
           +-- _timerSet() (AccessibilityNotifier.cpp:329)
                  |
                  +-- [delay=0] _emitEvents() (AccessibilityNotifier.cpp:371)
                  |      |
                  |      +-- NotifyWinEvent(EVENT_CONSOLE_UPDATE_SCROLL)
                  |
                  +-- [delay>0] SetThreadpoolTimerEx() -> _timerEmitMSAA() (AccessibilityNotifier.cpp:342)
                         |
                         +-- _emitEvents() (AccessibilityNotifier.cpp:371)
                                |
                                +-- NotifyWinEvent(EVENT_CONSOLE_UPDATE_SCROLL)
```

### データフロー図

```
[入力]                          [処理]                              [出力]

ビューポート位置変更        AccessibilityNotifier               WinEvent
(NewWindow座標)    ------>  ::ScrollViewport()           ------>  EVENT_CONSOLE_UPDATE_SCROLL
                            State.deltaX/Y蓄積                   (dx, dy パラメータ)
                            デバウンスタイマー                      |
                                                                  v
                                                          支援技術（NVDA等）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| AccessibilityNotifier.h | `src/host/AccessibilityNotifier.h` | ソース（ヘッダ） | AccessibilityNotifierクラスとState構造体の定義 |
| AccessibilityNotifier.cpp | `src/host/AccessibilityNotifier.cpp` | ソース（実装） | ScrollViewport、_emitEvents等の実装 |
| window.cpp | `src/interactivity/win32/window.cpp` | ソース（実装） | ChangeViewportからScrollViewportを呼び出す箇所 |
| CustomWindowMessages.h | `src/interactivity/win32/CustomWindowMessages.h` | ソース（ヘッダ） | CM_UPDATE_SCROLL_BARS等のカスタムメッセージ定義 |
| globals.h | `src/host/globals.h` | ソース（ヘッダ） | グローバルなaccessibilityNotifierインスタンスの定義 |
