Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions docs/design/clipboard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# クリップボード読み取り

Chrome 116相当以降を対象とし、Service Workerからの読み取りは
`readClipboard()` → `readOffscreenClipboard()` に統一する。
`clipboard.html` による表示タブ・ウィンドウでの読み取りは使用しない。

## 作成コストと再利用

- 最初の読み取りでOffscreen Documentを遅延作成する。
- 読み取り後もドキュメントを維持し、次回以降は同じドキュメントを再利用する。
- 読み取りは直列化し、同時要求による二重作成や結果の混在を防ぐ。
- Service Worker再起動時も `runtime.getContexts()` で既存ドキュメントを検出する。
- Chromeによってドキュメントが破棄された場合は、次の要求で再作成する。
- textareaの内容は読み取り直後に消去し、クリップボード本文を保持しない。
- 作成時に他のコンテキストが先に作成していた場合(単一ドキュメント制約のエラー)は、
既存ドキュメントをそのまま使用する。

## 失敗時の扱い

- 読み取りに失敗した場合は50ms間隔で最大3回まで再試行する。
- Offscreen Documentからの応答が `ClipboardResult` の形式でない場合はエラーとして扱う。
- textareaにフォーカスできない場合は、空文字を成功として返さずエラーとする。
- エラー時のトーストは、ユーザーが見ているタブへ表示する。
バックグラウンドタブで開く場合は、表示先ではなく実行元のタブへ通知する。

Offscreen Documentを表示先のタブ・ウィンドウへ転用するAPIはないため、
従来の「読み取り画面を表示先として転用する」最適化はそのまま移植できない。
代わりに、読み取り側のドキュメントを要求間で再利用する設計とする。

従来は、表示先タブ・ウィンドウで読み取った後、その画面を目的URLへ遷移させていた。
新方式では読み取り用のドキュメントを繰り返し再利用し、表示先は完成したURLで
一度だけ作成する。初回は非表示ドキュメントの作成待ちが必要だが、次回以降は
読み取りごとの作成・破棄と、表示先での中間ページの読み込みを避けられる。
ブラウザによるレンダラープロセスの割り当ては制御しないため、
初回を含めて常に高速になるとは保証しない。

## 読み取りAPI

Offscreen Documentはフォーカスできない。検証環境では
`navigator.clipboard.readText()` は存在するものの、
`Document is not focused` で失敗したため、`clipboardRead` 権限と
textareaへの `document.execCommand("paste")` を使用する。
APIを将来変更する際は、非フォーカス状態での読み取りが成立することを確認する。

- [Offscreen API](https://developer.chrome.com/docs/extensions/reference/api/offscreen)
- [runtime.getContexts](https://developer.chrome.com/docs/extensions/reference/api/runtime#method-getContexts)
55 changes: 55 additions & 0 deletions docs/test/e2e-test-spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,17 @@
| E2E-90 | ハブ | PageActionコマンド | インストール | 実装済 |
| E2E-91 | ハブ | 追加 | - | 実装済 |
| E2E-92 | ハブ | 削除 | - | 実装済 |
| E2E-100 | ショートカット | 検索・選択文字列 | onCommand以降 | 実装済 |
| E2E-101 | ショートカット | 日本語・空白・記号 | onCommand以降 | 実装済 |
| E2E-102 | ショートカット | クリップボード | onCommand以降 | 実装済 |
| E2E-103 | ショートカット | DO_NOTHING | onCommand以降 | 実装済 |
| E2E-104 | ショートカット | Content Scriptなし | onCommand以降 | 実装済 |
| E2E-105 | ショートカット | 登録状態 | onCommand以降 | 実装済 |
| E2E-106 | ショートカット | PageAction / Popup | Clipboard / 未選択 | 実装済 |
| E2E-107 | ショートカット | PageAction / BackgroundTab | Clipboard / 未選択 | 実装済 |
| E2E-108 | ショートカット | PageAction / Popup | SelectedText / 選択あり | 実装済 |
| E2E-109 | ショートカット | PageAction / Popup | SelectedText / 未選択 | 実装済 |
| E2E-110 | ショートカット | PageAction / BackgroundTab | SelectedText / 未選択 | 実装済 |

---

Expand Down Expand Up @@ -1048,3 +1059,47 @@
**期待動作**

- 削除したコマンドに対応する Hub ページのダウンロードボタンが復活する(「追加済み」表示が解除される)

### ショートカット(shortcut.spec.ts)

`chrome.commands.onCommand` に登録する `handleShortcutCommand` 以降を検証する。
E2EビルドでのみService Workerのグローバルへ関数を公開し、Playwrightの
`Worker.evaluate()` から呼び出す。IPCや外部Webページ向けの受付経路は追加しない。
本番ビルドでは公開処理が除去される。OSのキー入力・Chromeのイベント発火、
サイドパネルは対象外。各テストでOptionsPageから設定をインポートする。

| テストID | 手順 | 期待動作 |
| -------- | ------------------------------------------------------ | -------------------------------------------------------------------------------- |
| E2E-100 | TestPageで見出しを選択しslot_1を実行 | 割り当てた検索コマンドの結果が開き、選択文字列が検索パラメータに渡る |
| E2E-101 | 日本語・空白・記号を選択しslot_1を実行 | URLエンコードされた文字列が正しく渡る |
| E2E-102 | 未選択状態でクリップボードへ文字列を設定しslot_1を実行 | クリップボードの文字列で検索する |
| E2E-103 | 未選択状態でDO_NOTHINGのslot_2を実行し処理完了を待つ | タブ数・ID・URLが変化しない |
| E2E-104 | クリップボードを設定しchrome://versionでslot_1を実行 | Content Scriptを使わずService Workerから検索する |
| E2E-105 | Service Workerでchrome.commands.getAll()を実行 | slot_1〜slot_3の登録を確認する。キー文字列の値はOSや競合に依存するため固定しない |

#### PageActionの変数展開(E2E-106〜110)

各ケースで入力ステップを含むPageActionをOptionsPageからインポートし、
`slot_1` に割り当てる。未選択時の設定は `USE_CLIPBOARD` とする。
TestPageを開き、実際のクリップボードに日本語・記号・改行と、配列を含む
TypeScriptコードを設定する。`[KeyA]`、`{Enter}`、`[]`、`[[`、`{{` も含め、
キー操作として解釈されず、textareaに文字列のまま入力されることを検証する。
ショートカット実行後、入力欄の値が `Input: ` と期待する文字列に一致するまで待機する。
ウィンドウ種別がポップアップ/通常ウィンドウであることも検証する。
テスト側で対象ページをアクティブ化する操作は行わない。

バックグラウンドタブでは `readClipboard()` がOffscreen Documentで読み取り、
結果タブを非アクティブで作成する。E2E-107/110ではウィンドウが新規作成されないこと、
結果タブの非アクティブ状態と元ページのフォーカス維持も検証する。
全表示モードでOffscreenに統一し、Chrome 116相当以降を対応範囲とする。
E2E-107ではクリップボードを書き換えて再実行し、最新の入力値と
Offscreen Documentの `documentId` が同一であること(再利用)も検証する。
設計の詳細は [クリップボード読み取り](../design/clipboard.md) を参照。

| テストID | 選択状態 | 入力ステップ | 表示方法 | 期待する入力値の変数部分 |
| -------- | -------- | ------------------ | -------------------- | ------------------------------------------ |
| E2E-106 | 未選択 | `{{Clipboard}}` | ポップアップ | クリップボードの内容 |
| E2E-107 | 未選択 | `{{Clipboard}}` | バックグラウンドタブ | クリップボードの内容 |
| E2E-108 | 選択あり | `{{SelectedText}}` | ポップアップ | 選択テキスト(クリップボードとは異なる値) |
| E2E-109 | 未選択 | `{{SelectedText}}` | ポップアップ | クリップボードの内容 |
| E2E-110 | 未選択 | `{{SelectedText}}` | バックグラウンドタブ | クリップボードの内容 |
3 changes: 2 additions & 1 deletion docs/test/serviceWorker-test-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@

**テキスト処理:**

- ✅ SWD-44: 正常系: selectedTextが空でuseClipboardがtrueの場合、clipboardTextを使用
- ✅ SWD-44: 正常系: selectionFromClipboardがtrueの場合、clipboardTextをselectedTextとして使用
- ✅ SWD-44-b: 正常系: ステップのみがクリップボードを使う場合(clipboardForSteps)、selectedTextは空のまま
- ✅ SWD-45: 正常系: selectedTextが存在する場合はそのまま使用
- ✅ SWD-46: 正常系: userVariablesが正しく渡される

Expand Down
2 changes: 1 addition & 1 deletion packages/extension/e2e/link-preview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ test.describe("Link Preview", () => {
const href = await link.getAttribute("href")
expect(href).toBeTruthy()

// React 側が ready になってからネイティブ Shift+click
// Native Shift+click after the React side is ready
const [previewPage] = await Promise.all([
context.waitForEvent("page", { timeout: 5000 }),
await link.click({
Expand Down
Loading
Loading