From 55e581bab9b600e7c070ec596d3109fc897d2d86 Mon Sep 17 00:00:00 2001 From: tanaka takashi Date: Tue, 1 Sep 2026 20:10:20 +0900 Subject: feat: Firefox selection translation extension with local Ollama (initial version) Implement the complete initial version of a Firefox WebExtension (Manifest V3) that translates selected text on web pages using a local Ollama LLM and shows the result in a small popover near the selection. Specification alignment (Phase 0) - Unified Specifications.md / Design.md / Security.md on the same approach: Ollama REST API + Fetch API + NDJSON streaming (no ollama-js SDK dependency) - Fixed host_permissions to http://localhost/* and http://127.0.0.1/* because Firefox match patterns do not support port numbers (bug 1362809/1468162); the actual connection target is restricted to localhost/127.0.0.1 + any port by application-side validation (defense in depth, OLL-09) - Clipboard: use navigator.clipboard.writeText() only inside the copy button click (user activation); no deprecated execCommand fallback Implementation (zero dependencies, vanilla JS, no npm/Node required) - manifest.json: MV3, background.scripts + type:module (event page, no service_worker), action popup, options_ui, content_scripts for http/https, permissions: storage + localhost host permissions only - src/shared: constants (ports, limits, presets, error codes), message validation, settings schema (defaults / merge / sanitize / buildOptions / toApiThinking), system prompt builder (fixed prompt first + user prompt) - src/background: message-router (runtime.Port streaming + runtime.onMessage for model list), translation-session (single active request, abort previous on new request, stale chunk suppression), ollama-client (fetch against /api/tags and /api/chat with buffering NDJSON line parser tolerant to chunk boundaries inside JSON objects, AbortController, host re-validation before every request, HTTP status to error-code mapping), settings-service (storage.local load/save with validation and fallback to defaults) - src/content/content.js: single classic script (Firefox does not support ES modules in content scripts) with selection detection (selectionchange + mouseup + keyup, debounced), Shadow DOM action button, Shadow DOM popover with HIDDEN/LOADING/STREAMING/COMPLETED/ERROR state machine, port client with requestId-based stale chunk filtering, enabled-state sync via storage.onChanged; all dynamic text rendered via textContent only - src/popup + src/options: full settings UI (enable toggle, Ollama host, model list from Ollama with saved-model warning, target language, thinking mode, temperature slider 0.0-2.0 step 0.1, context length presets 4k/8k/16k/32k/128k/256k, seed random/fixed with integer value, user system prompt, advanced numeric options with steppers, stop sequences textarea) - icons generated by tools/make-icons.ps1 (System.Drawing, no external tools) - build.ps1 packages manifest.json + icons/ + src/ into dist zip, excluding tests/, doc/, ollama-js/ and tools/ Tests (71 passed, 0 failed) - In-browser test runner (tests/test.html) with custom describe/it/assert framework; no npm, no Node.js - Unit: constants sync between content mirror and shared module, Ollama host validation (localhost/127.0.0.1 + any port only; rejects external hosts, LAN IPs, https, credentials, paths), settings schema defaults and fallback, temperature 0.1-step validation, context length presets, seed modes, advanced option types, buildOptions omission of unset values and absence of tfs_z/mirostat fields, system prompt composition order, message validation with selection length boundary values - Integration: NDJSON streaming across awkward byte boundaries, thinking chunk forwarding, malformed line tolerance, error payload mapping, think/ options inclusion rules, model list, TranslationSession abort semantics, settings round-trip with storage.onChanged cache refresh, full port flow (started/chunk/completed), newer request aborts older with stale chunk discard, malformed message rejection, OLLAMA_UNAVAILABLE and INVALID_SETTINGS error paths, untrusted sender rejection Security audit (Design.md section 24 checklist: all pass) - No innerHTML/outerHTML/document.write/eval/new Function/localStorage - No remote JS/CSS/fonts/CDN resources; no inline scripts in extension pages - Ollama access limited to background; content scripts never fetch Ollama - Background validates message type/length/requestId; single concurrent translation enforced - Clipboard API only (no execCommand fallback) - Minimal permissions: storage + localhost host permissions Docs - README.md with build/load/usage instructions, API list, permission rationale - doc/Testing-Results.md with automated test results, security audit results, and a 14-item manual Firefox verification checklist (to be executed on a real Firefox + Ollama environment) --- doc/Design.md | 871 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 871 insertions(+) create mode 100644 doc/Design.md (limited to 'doc/Design.md') diff --git a/doc/Design.md b/doc/Design.md new file mode 100644 index 0000000..20de3e5 --- /dev/null +++ b/doc/Design.md @@ -0,0 +1,871 @@ +# Design.md + +## 1. 設計概要 + +本拡張は、Webページとの接点をContent Scriptに限定し、権限を伴うOllama通信と設定管理をBackgroundへ分離する。 + +```text +Web Page + │ + │ selection + ▼ +Content Script + │ runtime.Port + ▼ +Background + │ fetch / HTTP + ▼ +Ollama + │ streaming + ▼ +Background + │ runtime.Port + ▼ +Content Script + │ + ▼ +Shadow DOM Popover +``` + +設計原則: +- ページ由来データは信頼しない。 +- Content Scriptは最小責務とする。 +- Ollama通信はBackgroundのみ(Ollama REST API + Fetch API + NDJSON streaming)。 +- UI文字列はHTMLとして評価しない。 +- 翻訳要求は常に1-shot。 +- 同時翻訳は1件に制限。 +- 設定値はUIだけでなくBackgroundでも検証する。 +- Ollama接続先は初期版ではlocalhost / 127.0.0.1 + 任意ポートのみ許可する。 + +--- + +## 2. コンポーネント構成 + +| コンポーネント | 主責務 | +|---|---| +| `content/selection-controller` | Selection検出、Range位置取得 | +| `content/action-button` | 翻訳アイコン表示/非表示 | +| `content/translation-popover` | 翻訳結果UI、stream描画、copy/regenerate/close | +| `content/port-client` | Backgroundとの長期接続 | +| `background/message-router` | Content Scriptからのrequest受信・validation | +| `background/translation-session` | requestId、AbortController、active stream管理 | +| `background/ollama-client` | Ollama REST API呼び出し(fetch + NDJSON streaming) | +| `background/settings-service` | storage読書き、default、validation | +| `popup` | 有効/無効、簡易設定、Options起動 | +| `options` | 全設定UI | +| `shared/settings-schema` | 設定型、既定値、範囲 | +| `shared/messages` | runtime message型定義 | +| `shared/constants` | 最大選択長等 | + +--- + +## 3. 推奨ディレクトリ構成 + +```text +/ +├─ manifest.json +├─ package.json +├─ src/ +│ ├─ background/ +│ │ ├─ index.js +│ │ ├─ message-router.js +│ │ ├─ translation-session.js +│ │ ├─ ollama-client.js +│ │ └─ settings-service.js +│ ├─ content/ +│ │ ├─ index.js +│ │ ├─ selection-controller.js +│ │ ├─ action-button.js +│ │ ├─ translation-popover.js +│ │ ├─ port-client.js +│ │ └─ content.css +│ ├─ popup/ +│ │ ├─ popup.html +│ │ ├─ popup.js +│ │ └─ popup.css +│ ├─ options/ +│ │ ├─ options.html +│ │ ├─ options.js +│ │ └─ options.css +│ └─ shared/ +│ ├─ settings-schema.js +│ ├─ messages.js +│ ├─ prompt.js +│ └─ constants.js +├─ tests/ +│ ├─ unit/ +│ └─ integration/ +└─ docs/ + ├─ Specifications.md + └─ Design.md +``` + +実際のビルド方式に応じて配置は調整してよいが、責務分離は維持する。 + +--- + +## 4. Manifest設計 + +概念例: + +```json +{ + "manifest_version": 3, + "name": "Selected Text Translator", + "version": "0.1.0", + "permissions": ["storage"], + "host_permissions": ["http://localhost/*", "http://127.0.0.1/*"], + "background": { + "scripts": ["background.js"], + "type": "module" + }, + "content_scripts": [ + { + "matches": ["http://*/*", "https://*/*"], + "js": ["content.js"], + "css": ["content.css"], + "run_at": "document_idle" + } + ], + "action": { + "default_popup": "popup.html" + }, + "options_ui": { + "page": "options.html", + "open_in_tab": true + } +} +``` + +### 4.1 Manifest実装ルール +- `background.service_worker` は使用しない。 +- `browser_action` は使用しない。 +- `scripting` 権限は初期版では付与しない。 +- `tabs` 権限は付与しない。 +- localhost以外のhost permissionは初期版では付与しない。 +- Firefoxのmatch patternはポート番号指定に対応していない(Firefox bug 1362809 / 1468162)。`http://localhost/*` および `http://127.0.0.1/*` は任意ポートに一致するため、実際に通信してよい接続先はアプリ側Host validatorで `http://localhost:` / `http://127.0.0.1:` のみに限定する。 + +--- + +## 5. Content Script設計 + +### 5.1 Selection取得 + +標準DOM APIを使用する。 + +```js +const selection = window.getSelection(); +const text = selection?.toString().trim(); + +if (!text) return; + +const range = selection.getRangeAt(0); +const rect = range.getBoundingClientRect(); +``` + +### 5.2 検出イベント +初期実装候補: +- `selectionchange`: 選択状態更新用 +- `mouseup`: 選択確定後の表示タイミング用 +- `keyup`: Shift + Arrow等のキーボード選択対応用 + +過剰発火を避けるため、UI更新には軽いdebounceを適用してよい。 + +### 5.3 Action Button表示条件 + +```text +selection exists +AND trimmed text length > 0 +AND text length <= MAX_SELECTION_LENGTH +AND extension enabled +``` + +### 5.4 位置計算 +- `Range.getBoundingClientRect()` を基本とする。 +- 選択が複数行の場合でも最後/全体矩形を基準に配置する。 +- viewport端ではポップオーバーが画面外へ出ないよう補正する。 +- scroll/resize時は再配置または非表示化する。 + +--- + +## 6. UI分離設計 + +### 6.1 Shadow DOM + +ページCSSとの衝突を避けるため、Content Scriptが専用host要素を追加し、その内部にShadow DOMを作る。 + +```js +const host = document.createElement('div'); +document.documentElement.append(host); +const shadow = host.attachShadow({ mode: 'open' }); +``` + +`open/closed` は実装都合で選択可能だが、セキュリティ境界として依存しない。 + +### 6.2 DOM挿入ルール + +禁止: +```js +element.innerHTML = translatedText; +shadowRoot.innerHTML = translatedText; +``` + +許可: +```js +element.textContent = translatedText; +``` + +静的テンプレートHTMLを自前固定文字列として構築する場合も、外部入力を文字列連結しない。 + +--- + +## 7. 翻訳ポップオーバー設計 + +### 7.1 状態 + +```text +HIDDEN +READY +LOADING +STREAMING +COMPLETED +ERROR +``` + +### 7.2 表示内容 + +| 要素 | 内容 | +|---|---| +| Header | 翻訳結果 | +| Meta | Model名 / Ollama | +| Body | streaming翻訳本文 | +| Copy | 完了/途中結果をclipboardへコピー | +| Regenerate | 同じ選択文字列で再要求 | +| Close | ポップオーバー非表示 | + +### 7.3 UI挙動 +- Icon押下直後にLOADING表示。 +- 最初のchunk受信後STREAMINGへ遷移。 +- 完了イベントでCOMPLETED。 +- Abortされた旧requestのchunkはrequestId不一致なら無視。 +- Error時は再生成可能な状態に戻す。 + +--- + +## 8. Runtime通信設計 + +Streamingには `browser.runtime.connect()` / `runtime.Port` を使用する。 + +### 8.1 Port名 + +```text +translation-stream +``` + +### 8.2 Content → Background message + +#### translate + +```js +{ + type: 'translate', + requestId: 'uuid-or-random-id', + text: 'selected text' +} +``` + +#### cancel + +```js +{ + type: 'cancel', + requestId: '...' +} +``` + +### 8.3 Background → Content message + +#### started + +```js +{ + type: 'started', + requestId: '...', + model: '...' +} +``` + +#### chunk + +```js +{ + type: 'chunk', + requestId: '...', + text: 'partial translation' +} +``` + +#### completed + +```js +{ + type: 'completed', + requestId: '...' +} +``` + +#### error + +```js +{ + type: 'error', + requestId: '...', + code: 'OLLAMA_UNAVAILABLE', + message: '...' +} +``` + +### 8.4 Validation +Backgroundは最低限以下を確認する。 +- `type` が既知値か +- `requestId` がstringか +- `text` がstringか +- text lengthが上限以内か +- 現在のsender/portがContent Script由来か + +--- + +## 9. TranslationSession設計 + +Backgroundではactive translationを原則1件だけ保持する。 + +```js +class TranslationSession { + requestId; + abortController; + sourceText; + startedAt; +} +``` + +新しい翻訳要求: + +```text +translate(request B) + ↓ +active request Aあり? + ├─ yes → Abort A + └─ no + ↓ +request Bをactiveに設定 + ↓ +Ollama streaming開始 +``` + +### 9.1 古いchunk対策 +受信chunkごとにactive requestIdを確認し、不一致なら破棄する。 + +--- + +## 10. OllamaClient設計 + +### 10.1 API + +Ollama REST APIをFetch APIで直接呼び出す。`ollama-js`等のサードパーティSDKは使用しない。 + +概念例: + +```js +const response = await fetch(`${host}/api/chat`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model: settings.model, + messages: [ + { + role: 'system', + content: buildSystemPrompt(settings) + }, + { + role: 'user', + content: selectedText + } + ], + stream: true, + think: settings.thinking, // off時は省略 + options: buildOptions(settings) + }), + signal: abortController.signal +}); + +// NDJSONストリームをバッファリングしつつ行単位でパースする +const reader = response.body.getReader(); +const decoder = new TextDecoder(); +let buffer = ''; +for (;;) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + // bufferを行単位で分割し、完全な行のみJSON.parseする + // 末尾の不完全行は次回チャンクに持ち越す +} +``` + +### 10.2 System Prompt構築 + +```text +BUILTIN_SYSTEM_PROMPT + +[User translation preferences] +USER_SYSTEM_PROMPT +``` + +固定promptは常に先頭に置く。 + +### 10.3 buildOptions +未設定値はrequest objectへ含めない。 + +例: + +```js +{ + temperature: 0.2, + num_ctx: 16384, + seed: 42, + top_k: 40, + top_p: 0.9, + min_p: 0.05, + typical_p: 1.0, + repeat_last_n: 64, + repeat_penalty: 1.1, + presence_penalty: 0.0, + frequency_penalty: 0.0, + num_keep: 4, + num_predict: -1, + stop: [''] +} +``` + +Seed可変時は`seed`をoptionsへ含めない設計を優先する。 + +--- + +## 11. 設定スキーマ + +推奨形: + +```js +{ + enabled: true, + ollamaHost: 'http://localhost:11434', + model: '', + targetLanguage: 'ja', + thinking: false, + temperature: 0.2, + contextLength: 16384, + seedMode: 'random', + seedValue: null, + userSystemPrompt: '', + advanced: { + numKeep: null, + numPredict: null, + topK: null, + topP: null, + minP: null, + typicalP: null, + repeatLastN: null, + repeatPenalty: null, + presencePenalty: null, + frequencyPenalty: null, + stop: [] + } +} +``` + +--- + +## 12. 設定値validation + +### 12.1 Basic + +| enabled | boolean | +| ollamaHost | URL形式。**`http://localhost:` または `http://127.0.0.1:` のみ許可(schemeはhttp固定、外部ホスト・LAN IP・httpsは拒否)。必須制限** | +| model | non-empty string | +| targetLanguage | 定義済み値 | +| thinking | `false/true/low/medium/high` 相当 | +| temperature | 0.0〜2.0、0.1刻み | +| contextLength | `4096/8192/16384/32768/131072/262144` のいずれか | +| seedMode | `random/fixed` | +| seedValue | fixed時のみinteger | +| userSystemPrompt | string、最大長を設定 | + +### 12.2 Advanced + +推奨UI step: + +| 項目 | step | validation方針 | +|---|---:|---| +| num_keep | 1 | integer | +| num_predict | 1 | integer、-1許容 | +| top_k | 1 | integer、0以上 | +| top_p | 0.05 | float | +| min_p | 0.01 | float | +| typical_p | 0.05 | float | +| repeat_last_n | 1 | integer、-1許容 | +| repeat_penalty | 0.05 | float | +| presence_penalty | 0.05 | float | +| frequency_penalty | 0.05 | float | +| stop | - | string[] | + +Ollamaが明示的な最小/最大値を保証していない項目について、UI側で過度に狭い範囲を独自設定しない。型と明らかな不正値を中心に検証する。 + +--- + +## 13. Context Length UI設計 + +固定プリセット: + +```js +const CONTEXT_PRESETS = [ + 4096, + 8192, + 16384, + 32768, + 131072, + 262144 +]; +``` + +UI表示: + +```text +4k ─ 8k ─ 16k ─ 32k ─ 128k ─ 256k +``` + +実装上はrange inputのindex値を使用してよい。 + +```text +0 => 4096 +1 => 8192 +2 => 16384 +3 => 32768 +4 => 131072 +5 => 262144 +``` + +モデル自身が対応する最大contextを超える場合、Ollama側でエラーまたは制約される可能性があるため、そのエラーをUIへ返す。 + +--- + +## 14. Temperature UI設計 + +```text +min=0.0 +max=2.0 +step=0.1 +``` + +スライダー値は表示用数値と同期する。 + +```html + +``` + +--- + +## 15. Seed UI設計 + +```text +Seed +[ 可変 ] [ 固定 ] + +固定選択時: +固定値 (整数) [ 42 ▲▼ ] +``` + +- random時: seed optionを送らない。 +- fixed時: `seedValue` を整数として送る。 +- Seed Valueはnumber input + spinnerでよい。 + +--- + +## 16. Advanced UI設計 + +数値項目は`input type="number"`相当のUIとする。 + +例: + +```text +top_p +[ 0.90 ][▲] + [▼] +``` + +HTML native spinnerを使用できる場合は活用し、独自実装を増やさない。 + +stopは複数行textareaとし、1行1sequenceとして保存する。 + +--- + +## 17. SettingsService設計 + +### 17.1 保存 + +`browser.storage.local` を使用。 + +### 17.2 読み込み +1. default settings生成 +2. storage値取得 +3. merge +4. validation +5. 不正値はdefaultへフォールバック + +### 17.3 設定変更通知 +設定変更時に `browser.storage.onChanged` を使用し、Content Script / Background双方が必要な設定を更新できるようにする。 + +--- + +## 18. Security Design + +### 18.1 Trust Boundary + +```text +UNTRUSTED +Web Page / DOM / Selection + │ + ▼ +Content Script + │ structured messages only + ▼ +PRIVILEGED +Background / Ollama access / storage +``` + +### 18.2 XSS対策 +- 選択文字列をHTML化しない。 +- LLM出力をHTML化しない。 +- Markdown rendererを初期版では導入しない。 +- DOM挿入は`textContent`中心。 + +### 18.3 Prompt Injection対策 +翻訳用途ではページ本文を命令ではなくデータとして固定system promptで扱う。 + +ただしLLMの挙動を完全保証するセキュリティ境界としてPromptのみへ依存しない。 + +### 18.4 Remote Code禁止 +- CDN script禁止。 +- `eval`禁止。 +- `new Function`禁止。 +- remote import禁止。 +- npm依存はbundleに含める。 + +### 18.5 Storage +- `storage.local` は暗号化ストレージではない前提。 +- API key等の秘密情報は初期版では扱わない。 + +### 18.6 Message Validation +Content側のデータをBackgroundで再検証する。 + +### 18.7 Resource Abuse +- MAX_SELECTION_LENGTHを設ける。 +- active translationは1件。 +- 新規requestで旧request abort。 +- 選択だけではrequestを送らない。 + +--- + +## 19. Error Code設計 + +| Code | 意味 | +|---|---| +| `INVALID_REQUEST` | message形式不正 | +| `TEXT_EMPTY` | 空文字 | +| `TEXT_TOO_LONG` | 選択長超過 | +| `OLLAMA_UNAVAILABLE` | Ollama接続不可 | +| `MODEL_NOT_FOUND` | モデルなし | +| `INVALID_SETTINGS` | 設定値不正 | +| `REQUEST_ABORTED` | 新規要求等で中断 | +| `STREAM_ERROR` | streaming中エラー | +| `UNKNOWN_ERROR` | その他 | + +UIでは内部stack traceを表示しない。 + +--- + +## 20. Copy設計 + +Clipboard APIのみを使用する。`document.execCommand('copy')` はdeprecatedのため使用しない。 + +- コピーはポップオーバーのコピーボタンclick(user activation内)で発生するため、`navigator.clipboard.writeText()` をそのハンドラ内で直接呼び出す。 +- 書き込み失敗時は「コピーできませんでした」等の簡潔なメッセージをポップオーバーへ表示するのみとし、deprecated APIへフォールバックしない。 +- Firefox実機でclipboard動作を確認する。 + +コピー対象は現在表示中の翻訳プレーンテキストのみ。 + +--- + +## 21. モデル一覧取得 + +BackgroundのOllamaClientからモデル一覧取得APIを呼び、Settings UIへ返す。 + +要件: +- Ollama未起動時は空一覧 + 接続エラー表示。 +- 取得成功時はselect候補へ反映。 +- 保存済みmodelが一覧にない場合は警告表示。 + +--- + +## 22. 実装順序 + +### Phase 1: Skeleton +- Manifest V3 +- Background +- Content Script +- Popup / Options +- build/test基盤 + +### Phase 2: Selection UI +- Selection検出 +- Action button +- Shadow DOM +- Popover skeleton + +### Phase 3: Messaging +- runtime.Port +- message schema +- validation +- requestId + +### Phase 4: Ollama +- Ollama REST client(fetch + NDJSON streaming) +- model list +- chat stream +- Abort + +### Phase 5: Settings +- storage.local +- Basic settings +- Advanced settings +- Context slider +- Temperature slider +- Seed modes + +### Phase 6: Security Hardening +- textContent確認 +- CSP確認 +- message validation +- input length limits +- no remote code +- permissions audit + +### Phase 7: Tests +- unit +- integration +- Firefox実機 + +### Phase 8: Final Verification +- lint/test/build +- temporary add-on install +- acceptance criteriaを全件確認 + +--- + +## 23. テスト設計 + +### 23.1 Unit + +| 対象 | ケース | +|---|---| +| settings schema | default / valid / invalid | +| temperature | 0.1刻み / 範囲外 | +| contextLength | presetのみ許可 | +| seed | random / fixed / invalid integer | +| advanced options | null除外、数値反映 | +| prompt builder | fixed + user prompt結合 | +| message validation | malformed message拒否 | +| length limit | 境界値 | + +### 23.2 Integration + +Mock Ollama serverまたはmock clientを使い以下を確認。 + +```text +Content request +→ Background validation +→ Ollama stream +→ chunk forwarding +→ complete +``` + +Abortテスト: + +```text +request A start +request B start +A abort +B chunks only displayed +``` + +### 23.3 Firefox Manual/E2E + +対象ページ: +- 単純HTML +- 長文記事 +- 選択文字列にHTMLタグ表現を含むページ +- CSSが強く指定されたページ +- スクロール位置が深いページ + +確認: +- 選択位置近傍UI +- viewport端補正 +- scroll時挙動 +- selection解除 +- copy +- regenerate +- close +- settings persistence +- Ollama down/up + +--- + +## 24. Firefox API監査チェックリスト + +実装完了時に以下を確認する。 + +- [ ] Manifest V3 +- [ ] `action` 使用 +- [ ] `background.scripts` 使用 +- [ ] `background.service_worker` 不使用 +- [ ] `browser_action` 不使用 +- [ ] `tabs.executeScript()` 不使用 +- [ ] `runtime.getBackgroundPage()` 不使用 +- [ ] `window.localStorage` 不使用 +- [ ] `storage.local` 使用 +- [ ] `runtime.Port` streaming通信 +- [ ] Content ScriptからOllama fetchなし +- [ ] `eval` / `new Function`なし +- [ ] Remote JSなし +- [ ] LLM出力への`innerHTML`なし +- [ ] 不要permissionsなし +- [ ] iframe初期対応なし + +--- + +## 25. 完了時にLLMへ要求する成果物 + +実装担当LLMは最終的に以下を提示すること。 + +1. 実装済みソースコード一式 +2. `manifest.json` +3. build手順 +4. Firefox一時アドオンとしてのロード手順 +5. Ollama起動/モデル準備手順 +6. 単体テスト結果 +7. 結合テスト結果 +8. Firefox実機動作確認結果 +9. 使用WebExtensions API一覧 +10. 要求permissions一覧と理由 +11. セキュリティチェック結果 +12. 未解決事項・既知制限 + +テストを実行せず「実装したので動くはず」として完了扱いにしてはならない。実際に実行可能なテストは実行し、Firefox実機確認項目も可能な範囲で実行結果を記録する。 -- cgit v1.2.3