Google Sheetsはどこまで外から操作できる? DOM click / CDP / Internal Actionを比較する
Synthetic DOM event、Chrome DevTools Protocol、Sheets-owned actionを実機比較し、動く方式でも採用しなかった理由まで検証する。
Google Sheetsをキーボード中心で操作するChrome拡張 Spex を作っています。
開発初期の実行モデルはかなり単純でした。
Alt sequence
↓
command
↓
対象DOMを探す
↓
click / mouse event
BoldやUndoのような一部操作では、これで動きました。
ところがshortcutを増やすと、同じ方法では動かないGoogle Sheets操作が出てきました。
Playwrightから本物のclickをすると開く。
人間がクリックしても当然開く。
でもcontent scriptから、
mousedown
mouseup
click
を送っても何も起きない。
ここからかなり遠回りしました。
「DOM要素があるならclickすればよい」が成立しなかった
実際に差が出た操作には、例えば次のようなものがありました。
- 検索と置換
- 条件付き書式
- キーボードショートカット
- シート追加
- チェックボックス挿入
- 一部の書式操作
Playwrightのtrusted clickではresult UIが開きます。
一方、content scriptと同じsynthetic DOM eventでは起動しませんでした。
この時点で、
selectorが間違っている
問題ではありません。
要素は見つかっています。
イベントもdispatchされています。
でもhost applicationがその入力をユーザー操作として受け付けていない。
まず chrome.debugger + CDP Inputを試した
技術的に一番分かりやすい解決策として、Chrome DevTools ProtocolのInputを試しました。
chrome.debugger でtabへattachし、CDPからmouse / keyboard inputを送れば、browser側に近い入力として扱えます。
prototypeを作って検証したところ、flat menuもnested menuも操作できました。
つまり問題は解けます。
ここで一度、かなり魅力的に見えました。
synthetic DOM event
-> 動かない
CDP Input
-> 動く
なら、CDPを使えばよい。
しかしproduction採用はやめました。
技術的に動くことと、Chrome拡張として採用できることは別だった
chrome.debugger を使うには強いpermissionが必要です。
Spexの目的は、Google Sheets上でkeyboard operationを補助することです。
そのためだけにdebugger権限を要求すると、製品のpermission storyが一気に重くなります。
ユーザーから見ると、
Spreadsheetのキーボード操作を追加する拡張
なのに、
browser tabへdebuggerとしてattachできる
permissionを要求することになります。
技術的には合理的でも、製品としての納得感がありませんでした。
そこでprototypeは残しつつ、production manifestからは完全に外しました。
現在もrepository内では、比較・回帰調査用のexperimentとして扱っています。
「権限を増やす」前に、host application自身の実行経路を調べた
次に調べたのが、Google Sheetsページ自身が操作をどう実行しているかです。
Google Sheetsのmenu itemには、page world側で対応するcontrolが存在します。
そのcontrol自身が持つaction dispatch経路を能力ベースで解決できれば、CDP InputなしでもSheets自身のactionを起動できます。
ただし、ここで別の罠があります。
Google Sheetsの内部実装へ固定依存すると、次の更新ですぐ壊れます。
例えばminifyされた、
Ko
ce
De
Ka
のようなmethod名を呼ぶ設計にはしませんでした。
同様に、たまたま現在存在する内部property名も正本にはしません。
名前ではなく「できること」でcontrolを探す
現在のSpexでは、概念的には次のような考え方です。
Isolated world
↓
対象DOM identityを決める
↓
一時tokenでMAIN worldへ対象を伝える
↓
page-owned controlを探索
↓
必要なcapabilityがあるか確認
↓
action dispatch
ポイントは、
このproperty名があるからこのclassだ
ではなく、
このDOMに紐づくcontrolとして必要な振る舞いを持っているか
を見ることです。
解決できなければ実行しません。
いわゆるfail closedです。
MAIN worldを使えば何でもしてよい、でもない
Chrome拡張ではisolated worldとpage worldの境界があります。
Google Sheets自身のruntime objectへ触るため、必要な部分だけMAIN world bridgeを使います。
しかし、Spexのcoreやshortcut machineまでpage worldへ移すわけではありません。
概念的には、
Shortcut Machine
↓
Semantic Command
↓
GoogleSheetsAdapter
↓
Page Action bridge
↓
MAIN world capability detection
という境界です。
Google Sheets内部の事情はできるだけAdapterより下へ閉じ込めます。
結局、実行方式を一つに統一しなかった
ここまで調べると、すべてのcommandをInternal Actionへ寄せたくなります。
それもやめました。
操作によって、一番単純で検証しやすい方法が違うからです。
現在は概念的に次の優先順位があります。
1. Direct DOM
安定したtoolbar controlがあり、結果までobservableなもの。
例:
Bold
Italic
Undo
2. Sheets Native Shortcut
Google Sheets自身のkeyboard shortcutを使った方が自然なもの。
3. Sheets Internal Action
synthetic DOM eventでは起動しないが、page-owned controlを安全に解決できるもの。
4. Command-specific strategy
clipboard、download、printなど、結果検証が別問題になるもの。
「一つのexecutorですべて動かす」より、commandごとに最小の権限・最小のhackで実行する方を選びました。
発火できても、成功扱いしない
Internal Actionを見つけたことで、また一つ罠があります。
dispatchEvent("action")
が例外なく終わったとしても、commandが成功したとは限りません。
そのため成功判定は別です。
例えば、
InsertSheet
-> sheet tab countが増えたか
Conditional Formatting
-> sidebarが実際に開いたか
Checkbox
-> target cellの状態が変わったか
のように、Google Sheets側のobservable resultを確認します。
実行経路とverificationを分けたことで、Internal Actionが将来壊れた場合でも「成功したふり」をしにくくなります。
debugger prototypeを残した理由
採用しなかったprototypeは消してもよかったのですが、repositoryには残しています。
理由は二つです。
1. 制約の再確認に使える
将来Google Sheets側の挙動が変わったとき、
DOM synthetic eventも動かない
Internal Actionも解決できない
CDP Inputなら動く
のか、
CDPでも動かない
のかで問題の場所が違います。
2. 「なぜdebugger permissionを使わないのか」の証拠になる
単に怖そうだから採用しなかったのではなく、動くことを確認した上で製品要件から却下したという履歴を残せます。
これは後から設計判断を見直すときに役立ちます。
production permissionは小さいままにした
Spex productionの権限は、keyboard productivityというsingle purposeに必要な範囲へ絞っています。
少なくともこのtrusted-input問題を解決するために、
debugger- Google OAuth
を追加する方針は採っていません。
難しい操作があるからといって、permissionを増やして万能executorを作るのではなく、対応できないcommandは対応済みにしません。
これは機能数より重要だと考えています。
この試行錯誤から得たこと
今回一番面白かったのは、技術的な壁そのものより、解けた後にその解決策を捨てたことでした。
開発では、
動かなかったものを動かした
時点で達成感があります。
でもproduct engineeringでは、さらに、
動くか
権限は妥当か
保守できるか
壊れたとき安全か
成功を検証できるか
を通す必要があります。
CDP Inputは「動く」を満たしました。
しかしSpexでは、permission UXの時点でproduction candidateから外しました。
その結果、少し遠回りして、現在の複数execution strategyへ落ち着いています。
一般化すると
Chrome拡張で既存Webアプリを操作すると、synthetic eventの限界へ当たることがあります。
そのとき、
より強い権限なら解ける
は、必ずしも最終解ではありません。
特に、ユーザーの作業データへ触れるWebアプリ上では、技術的なcapabilityを増やすほどpermission explanationも重くなります。
最も強い手段ではなく、ユーザーへ説明できる最小の手段を選ぶ。
Spexでは、chrome.debugger prototypeを作ったことで、その判断を具体的にできました。
Spex
このexecution strategyはGoogle Sheets向けChrome拡張Spexで使っています。
- Product: https://spex.kikuta.dev/ja
- Chrome Web Store: https://chromewebstore.google.com/detail/gahnolkboklnjhnaahjdkingoejbepcc