• 作成:

Firefox向けDeepLブラウザ拡張でC-'にショートカットキーを割り当てたかったのですが失敗しました

前提

Firefox向けのDeepL公式拡張がリリースされていたのをついさっき知りました。 DeepL Firefox Extension

ChatGPT Plusを契約したからDeepLは良いかなと契約当初は思っていたのですが、翻訳速度やwebサイトごと翻訳(これはPro限定ですが…)を考えるとDeepLにもまだまだ使いみちはありそうです。

これまで、 brookhong/Surfingkeys: Map your keys for web surfing, expand your browser with javascript and keyboard. の設定で、

mapkey("<Ctrl-'>", "DeepL", () => {
  const selection = window.getSelection().toString();
  if (selection !== "") {
    tabOpenLink(
      `https://www.deepl.com/translator#en/ja/${encodeURIComponent(selection).replaceAll("%2F", "\\%2F")}` // DeepLはスラッシュを特別扱いするためエスケープする。
    );
  }
});

として選択した範囲でDeepLに飛ぶようにしていたのですが、いきなりDeepLのサイトに飛ぶのも色々手間ですし、ちゃんとエンコードをしているはずなのにも関わらず、きちんと入力がされないことがありました。

公式拡張が使えればそれに越したことはありません。

問題

Firefoxでは、開発者が個別に拡張機能のショートカットキーを設定する必要はなく、ブラウザ側で統一的に管理する機能が提供されています。

拡張機能のショートカットキーの管理

しかし、クオートを含むキーバインドはこの機能では入力できません。

このページのJavaScriptをちらっと見たのですが、ソースコードを見る限り、 Quote(')は対応していない素振りを見せています。

  const remapKeys = {
    ",": "Comma",
    ".": "Period",
    " ": "Space",
  };

さてDvorak入力を使っている自分にとって、右手のトラックボールで選択して左手だけで翻訳に移れるクオートでの入力は譲ることが出来ません。

探索

Firefoxの拡張機能には他の拡張機能にメッセージを送るbrowser.runtime.sendMessageという機能があり、これをSurfingkeysから発行すれば、同じキー制御で実行可能だろうと考えました。

問題は送るべきアクション名が当然ながらドキュメント化されていないことです。 DeepLの拡張機能のソースコードを読んでみても、オープンソースではないためか、コメントが少なく、解析が困難でしたが、 Firefoxの拡張機能管理画面でテキストの翻訳``nametrigger-translationになっていたので、そこから検索してみたらそれっぽいのが見つかりました。

/**
 * Command processing listener. Currently used to process shortcuts.
 *
 * @param string command
 */
export function commandListener(command) {
  console.log(`Command "${command}" triggered`)
  switch (command) {
    case SHORTCUT_TYPES.triggerTranslation:
      chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
        chrome.tabs.sendMessage(tabs[0].id, {
          action: "dlTriggerTranslationShortcut",
        })
      })
      break
    case SHORTCUT_TYPES.changeLanguage:
      chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
        chrome.tabs.sendMessage(tabs[0].id, {
          action: "dlChangeLanguageShortcut",
        })
      })
      break
  }
}

これの内部で行っていることを実行すれば良さそうですが、問題はSurfingkeysからどうやってtabsのAPIにアクセスするかですね…

tabsを無理やり実行する必要は無いのではと思いつきました。 tabsidを取っているのはクロスプラットホームだから仕方なくやっていることなので。 Firefoxしか考慮しなければ問題なさそうです。

要するに以下で良いのではと考えました。

const deeplId = "firefox-extension@deepl.com";

mapkey("<Ctrl-'>", "DeepL", () => {
  browser.runtime.sendMessage(deeplId, {
    action: "dlTriggerTranslationShortcut",
  });
});

しかしこれだとダメなようです。

Error: Could not establish connection. Receiving end does not exist.

というエラーが出てきてしまいます。

調べてみるとこれはManifest v3からの非同期エラーを制御してない時に出てくるエラーのようですね。

他にもdlRequestInlineTranslationなどをactionに設定して試してみましたが、 requestのフィールドが足りないのか、同じエラーメッセージが表示されてうまくいきませんでした。

TypeScriptのソースコードとかが読めないので、足りない型が即座に分からない。

あとエラーメッセージが不親切すぎますね…

content scriptの方を見れば分かりそう

content scriptの方から呼び出しているので、 translateInlineTextを参考にして、以下のように書いてみました。

const deeplId = "firefox-extension@deepl.com";

mapkey("<Ctrl-'>", "DeepL", () => {
  const requestString = window.getSelection().toString();
  browser.runtime.sendMessage(deeplId, {
    action: "dlRequestInlineTranslation",
    payload: {
      requests: [
        {
          text: requestString,
        },
      ],
      domainName: window.location.hostname,
      trigger: "inline",
      sourceLang: "EN-US",
    },
  });
});

しかしこれもやはりうまくいきませんでした。

もう少し深掘りすれば可能かもしれませんが、もうこれを頑張ってどうにかするよりは、 Firefoxにパッチを送ってQuoteを受け入れ可能にしたほうが、まだ正攻法な気がしてきます。

今回の工数だけならDeepL拡張を弄るだけのほうが良さそうですが、 DeepL拡張の内部構造が変わった場合に、内部の非公開APIを使う、モンキーパッチ気味なこの手法はすぐに壊れてしまうでしょう。

諦めて暫くは別のキーバインドで運用することにします。 C-'はChatGPTに割り当てたいなとも思っていましたし。