Conversation
Co-authored-by: ujiro99 <677231+ujiro99@users.noreply.github.com>
Co-authored-by: ujiro99 <677231+ujiro99@users.noreply.github.com>
Co-authored-by: ujiro99 <677231+ujiro99@users.noreply.github.com>
プルリクエストレビューこのPRは、ChromeのサイドパネルAPIを使用してURLを開く新しい機能を追加しています。全体的な実装は良好ですが、いくつかの改善点とセキュリティ上の懸念があります。 ✅ 良い点
🔴 重大な問題1. セキュリティ上の懸念: XSS脆弱性のリスク// packages/extension/src/services/chrome.ts:611-614
chrome.sidePanel.setOptions({
tabId: targetTabId,
path: toUrl(url), // ⚠️ ユーザー入力が直接pathに使用されている
enabled: true,
})問題点: 推奨される修正:
2. リソースリークの可能性// packages/extension/src/services/chrome.ts:630-653
export const closeSidePanel = async (tabId: number): Promise<void> => {
const cleanup = async () => {
try {
await BgData.update((data) => ({
sidePanelTabs: data.sidePanelTabs.filter((id) => id !== tabId),
}))
await chrome.sidePanel.setOptions({
tabId: tabId,
enabled: false,
})
} catch (e) {
console.warn("Failed to cleanup side panel:", e)
} finally {
chrome.sidePanel.onClosed.removeListener(cleanup) // ⚠️
}
}
try {
chrome.sidePanel.onClosed.addListener(cleanup)
await chrome.sidePanel.close({ tabId: tabId })
} catch (e) {
console.warn("Failed to close side panel:", e)
}
}問題点:
推奨される修正: export const closeSidePanel = async (tabId: number): Promise<void> => {
const cleanup = () => {
chrome.sidePanel.onClosed.removeListener(cleanup)
BgData.update((data) => ({
sidePanelTabs: data.sidePanelTabs.filter((id) => id !== tabId),
})).catch(console.error)
}
try {
chrome.sidePanel.onClosed.addListener(cleanup)
await chrome.sidePanel.close({ tabId: tabId })
} catch (e) {
chrome.sidePanel.onClosed.removeListener(cleanup) // エラー時も確実に削除
console.warn("Failed to close side panel:", e)
}
}
|
Pull Request Review: サイドパネル機能の追加このPRでは、Chrome拡張機能にサイドパネル表示機能を追加し、自動非表示機能を実装しています。全体的に良好な実装ですが、いくつか改善すべき点を見つけました。 🟢 良い点アーキテクチャ
実装の堅牢性
🔴 重大な問題1. 競合状態のリスク (helper.ts:106-137)
現在のコード: export const openSidePanel = (
param: OpenSidePanelProps,
sender: Sender,
): boolean => {
// ...async operations...
return false // ❌ 非同期処理を開始しているのにfalseを返している
}推奨される修正: export const openSidePanel = (
param: OpenSidePanelProps,
sender: Sender,
response: (res: boolean) => void,
): boolean => {
const tabId = sender.tab?.id
_openSidePanel({ ...param, tabId })
.then(() => incrementCommandExecutionCount(tabId))
.then(() => {
if (tabId) {
return BgData.update((data) => ({
sidePanelTabs: data.sidePanelTabs.includes(tabId)
? data.sidePanelTabs
: [...data.sidePanelTabs, tabId],
}))
}
})
.then(() => response(true))
.catch((error) => {
console.error('[ActionHelper.openSidePanel] Error:', error)
response(false)
})
return true // ✅ 非同期処理を示すためtrueを返す
}2. useSidePanelAutoCloseの論理エラー (useSidePanelAutoClose.ts:6-31)このフックには複数の問題があります: a) イベントリスナーのタイミング問題サイドパネルが表示された直後にfocus/clickイベントリスナーを追加していますが、これにより即座にサイドパネルが閉じられる可能性があります。 b) ウィンドウフォーカスイベントの誤解
c) クリックイベントの不必要な使用すべてのクリックでサイドパネルを閉じるのは、UX的に問題があります。 推奨される修正: // background_script.tsに追加
chrome.windows.onFocusChanged.addListener(async (windowId: number) => {
// 既存のコード...
// Side panel auto-hide機能
const settings = await enhancedSettings.get()
if (settings.windowOption.sidePanelAutoHide) {
const [currentTab] = await chrome.tabs.query({
active: true,
windowId: windowId
})
if (currentTab?.id) {
const bgData = BgData.get()
if (bgData.sidePanelTabs.includes(currentTab.id)) {
await closeSidePanel(currentTab.id)
}
}
}
})そして 🟡 中程度の問題3. テストカバレッジの不足新しく追加された機能に対する専用のテストがありません:
推奨アクション:
4. タブURLの変更時のクリーンアップ (background_script.ts)
推奨される修正: chrome.tabs.onUpdated.addListener((tabId, changeInfo) => {
if (changeInfo.url) {
// URL変更時にsidePanelTabsからタブIDを削除
BgData.update((data) => ({
sidePanelTabs: data.sidePanelTabs.filter((id) => id \!== tabId),
}))
}
})5. chrome.sidePanel.setOptions()のエラーハンドリング不足 (chrome.ts:612-616)
推奨される修正: try {
await chrome.sidePanel.setOptions({
tabId: targetTabId,
path: toUrl(url),
enabled: true,
})
await chrome.sidePanel.open({ tabId: targetTabId })
} catch (error) {
console.error('Failed to open side panel:', error)
throw error
}🟢 軽微な改善提案6. 型定義の改善
推奨される修正: export type OpenSidePanelProps = {
url: string | UrlParam
tabId: number // オプショナルを削除
}7. マジックナンバーの定数化closeSidePanelの onClosed リスナーのクリーンアップパターンは良好ですが、コメントで意図を明確にすると良いでしょう。 8. 国際化メッセージの確認多言語対応ファイルに追加されたメッセージが適切に翻訳されているか確認してください。特に自動翻訳を使用している場合は、ネイティブスピーカーによるレビューを推奨します。 🔒 セキュリティ考慮事項✅ 問題なし: URLのサニタイズは 📊 パフォーマンス考慮事項✅ 概ね良好: BgData.updateの使用頻度は適切で、不要なストレージ操作はありません。
📝 まとめ必須の修正:
強く推奨:
このPRは有用な機能追加ですが、上記の問題を修正してからマージすることを強く推奨します。特に1と2は競合状態やUX問題を引き起こす可能性があるため、優先的に対処してください。 レビューは packages/extension 内のコードを中心に実施しました。CLAUDE.mdの規約に従い、TypeScriptのベストプラクティスと Chrome 拡張機能の Manifest V3 の制約を考慮しています。 |
PR #274 コードレビューサイドパネル機能の実装についてレビューしました。全体的に構造は適切ですが、いくつかの問題点と改善提案があります。 🔴 重要な問題点1.
|
PR レビュー: Add sidePanel open mode with focus-based auto-hide
🐛 バグ / 潜在的な問題1.
|
| 優先度 | 項目 |
|---|---|
| 🔴 高 | setOptions() の await 欠落(バグ) |
| 🔴 高 | Auto-hide トリガーの実装欠如または説明不足 |
| 🟡 中 | getTabId の return 値(セマンティクス違反) |
| 🟡 中 | Promise チェーンのエラーハンドリング |
| 🟡 中 | sidePanelAutoHide デフォルト値の不整合 |
| 🟢 低 | URL プロトコル許可リスト方式への変更 |
| 🟢 低 | .gitignore の /.git エントリ削除 |
| 🟢 低 | テストカバレッジの追加 |
PR レビュー: Add sidePanel open mode全体的な実装の方向性は良好です。Chrome Side Panel API との統合、IPC 通信の拡張、多言語対応が適切に行われています。ただし、いくつか重要な問題点があります。 クリティカルバグ1. openSidePanel ヘルパーでの response(true) 未呼び出し問題
tabId が設定されている(通常ケース)場合、BgData.update() が成功しても response(true) が呼ばれません。IPC チャンネルが永遠に開いたままになります。 現在のコード: .then(() => {
if (tabId) {
return BgData.update(...) // BgData更新後、response(true) が呼ばれない
}
response(true) // これは !tabId の場合のみ実行される
})修正案: .then(() => {
if (tabId) {
return BgData.update((data) => ({
sidePanelTabs: data.sidePanelTabs.includes(tabId)
? data.sidePanelTabs
: [...data.sidePanelTabs, tabId],
}))
}
})
.then(() => {
response(true) // 常にここで呼ぶ
})2. chrome.sidePanel.setOptions のレースコンディション
setOptions() を await しないまま open() を呼び出すため、パネルが開く前に path が設定されていない可能性があります。ユーザージェスチャーの制約で同期的に呼ぶ必要がある点は理解できますが、Promise が pending な状態で open() が先行する可能性があります。実際の Chrome API の挙動上、同一イベントループ内であれば問題ない可能性もありますが、理由を明示的にコメントで補足することを推奨します。 3. テストでの response コールバック未渡し
NSP-06, NSP-07, NSP-08 では navigateSidePanel(param, sender) を2引数で呼んでいますが、関数の定義は3引数 (param, sender, response) を要求しています。 非同期チェーン内で response(true) が呼ばれた際に TypeError: response is not a function が発生し、unhandled promise rejection となります。テスト自体は通過するかもしれませんが、これはバグを隠蔽しています。 修正案: const response = vi.fn()
const result = navigateSidePanel(param, sender, response)
// ...
await new Promise((resolve) => setTimeout(resolve, 100))
expect(response).toHaveBeenCalledWith(true)機能未実装4. サイドパネルの自動非表示機能が実装されていないIssue の要件:
現在の実装では BgCommand.closeSidePanel と sidePanelAutoHide 設定は存在しますが、コンテンツスクリプト側でフォーカス/visibility イベントを検知して closeSidePanel を送信するコードがありません。useSidePanelNavigation.ts にもこのロジックがありません。また、設定UIへの sidePanelAutoHide の追加も見当たりません。 設計上の懸念点5. updateSidePanelUrl のナビゲーション動作chrome.sidePanel.setOptions() はパネルが次に開くときの設定を変更するAPIです。既に開いているパネルの現在表示URLをこの方法でナビゲートできるか確認が必要です。もし機能しない場合、リンクのインターセプト機能が期待通りに動作しません。 6. PAGE_ACTION_OPEN_MODE への SIDE_PANEL 追加
Page Action はブラウザ自動化シーケンスであり、URLを開くモードとは異なる概念です。SIDE_PANEL を Page Action のオープンモードとして使用するユースケースが明確でないため、意図的な追加かどうか確認が必要です。 7. BgData.watch が未使用追加された BgData.watch メソッドはPR内で使用されていません。将来の実装のために追加された場合はコメントで補足するか、実際に使用するまで追加を保留することを推奨します。 軽微な指摘
良い点
まとめ優先度の高い修正事項:
|
PR レビュー: Add sidePanel open mode (#274)Chrome Side Panel APIを使ったサイドパネル開きモードの実装をレビューしました。全体的に設計は適切ですが、いくつか改善点があります。 🔴 重大な問題1. AI設定ファイルがコミットされている
対処: これらのファイルをコミットから除外し、 🟡 重要な問題2.
|
コードレビュー: Add sidePanel open mode全体として、Chrome Side Panel APIを活用した新しいオープンモードの追加は良い方向性です。IPC パターンの一貫した使用、セキュリティ検証(プロトコルフィルタリング)、テストカバレッジの追加なども評価できます。ただし、動作に影響する可能性があるいくつかの問題を指摘します。 🔴 Critical1.
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #274 +/- ##
==========================================
+ Coverage 17.45% 17.79% +0.34%
==========================================
Files 304 307 +3
Lines 28068 28408 +340
Branches 1147 1185 +38
==========================================
+ Hits 4900 5056 +156
- Misses 23168 23352 +184 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
コードレビュー全体的に、Chrome Side Panel APIの統合として丁寧に実装されており、テストカバレッジも充実しています。ただし、いくつか対処が必要な点があります。 Critical: chrome.sidePanel.setOptions の path に外部URLを指定している可能性packages/extension/src/services/chrome.ts Chrome の sidePanel.setOptions の path フィールドは、拡張機能パッケージ内の相対パス(HTMLファイルへのパス)を期待しています。外部URL(例: https://google.com)を渡した場合、サイドパネルが空白になるか正常に機能しない可能性があります。 openSidePanel では path: toUrl(url) で、updateSidePanelUrl でも path: url で外部URLを設定しています。外部URLを表示するためには、拡張機能の side_panel.html 内でiframeを使うか、Chrome の最新APIを確認する必要があります。実装が意図通りに動作することを実際のブラウザで確認済みでしょうか? Bug: setOptions を await せずに open() を呼んでいるpackages/extension/src/services/chrome.ts chrome.sidePanel.setOptions() が await されていない状態で chrome.sidePanel.open() が呼ばれています。コメントには「ユーザージェスチャー内で実行する必要があるため await しない」とありますが、setOptions() はユーザージェスチャーを必要としないはずです。open() が呼ばれた時点で前の setOptions がまだ処理中の場合、古い設定で開かれるレースコンディションが生じます。 セキュリティ: URLバリデーションの不整合useSidePanelNavigation.ts では許可リスト方式 (["http:", "https:"] のみ許可) を使用していますが、helper.ts の navigateSidePanel では拒否リスト方式 (javascript: と data: のみ拒否) を使用しています。 バックグラウンドスクリプト側では ftp:、blob: 等も許容してしまいます。防御の深さを保つため、バックグラウンド側も許可リスト方式に統一することを推奨します。 未使用コード: BgData.watch()packages/extension/src/services/backgroundData.ts 追加された BgData.watch() メソッドがこのPRの変更のどこでも使用されていません。後で使う予定であれば、別PRで追加するか、使用箇所とセットで含めてください。 .gitignore の問題.gitignore に .git が追加されています。.git ディレクトリはGitリポジトリのメタデータそのものであり、通常は .gitignore に記述すべきものではありません。誤ってコミットされたものではないでしょうか? 冗長なコードpackages/extension/src/services/chrome.ts で const targetTabId = tabId という単純なエイリアスが作られています。tabId を直接使用してください。 Promiseの未処理packages/extension/src/action/sidePanel.ts Ipc.send() の返すPromiseが await されておらず、エラーが無視されます。await するか .catch() でエラーハンドリングを追加してください。 良い点
|
Adds
OPEN_MODE.SIDE_PANELto open URLs in Chrome's side panel API, enabling side-by-side viewing with the current page.Core Implementation
OPEN_MODE.SIDE_PANELin shared constants and extension mappingsaction/sidePanel.tswith IPC integration viaBgCommand.openSidePanelopenSidePanel()inservices/chrome.tsusingchrome.sidePanel.setOptions()andchrome.sidePanel.open()Permissions
"sidePanel"tomanifest.jsonpermissions arrayWarning
Firewall rules blocked me from connecting to one or more addresses (expand for details)
I tried to connect to the following addresses, but was blocked by firewall rules:
www.google-analytics.com/opt/hostedtoolcache/node/24.13.0/x64/bin/node /opt/hostedtoolcache/node/24.13.0/x64/bin/node --conditions node --conditions development /home/REDACTED/work/selection-command/selection-command/node_modules/tinypool/dist/entry/process.js +ujiro99@users.noreply.github.com> tnet/tools/git(dns block)/opt/hostedtoolcache/node/24.13.0/x64/bin/node /opt/hostedtoolcache/node/24.13.0/x64/bin/node --conditions node --conditions development /home/REDACTED/work/selection-command/selection-command/node_modules/tinypool/dist/entry/process.js --global k/_temp/ghcca-node/node/bin/bash/var/lib/dpkg/info/libgpm2:amd64.list pull.rebase(dns block)If you need me to access, download, or install something from one of these locations, you can either:
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.