Firefoxの拡張機能でコンテキストメニューをいじってみる

Firefoxの拡張機能を作ってみたので、作り方をまとめてみました。
この記事はFirefox 82.0.2で動作確認しています。
    
# 作成する拡張機能
ページの翻訳機能と、文章の翻訳機能を作ります。
右クリックで出るコンテキストメニューをクリックすると、翻訳ページに遷移する拡張機能を作ります。
ページ翻訳機能では、ページ全体を翻訳したページに、文章の翻訳機能は、選択した文章を翻訳したページに遷移します。
    
# サンプルを動かす
とりあえず動かしてみるため、公式のサンプルを動かして、動作確認してみます。

下のリンクからサンプル集をクローンします。
https://github.com/mdn/webextensions-examples

サンプルは大量にあるのですが、今回はMenu APIを使うので、
その中の、menu-demoフォルダを使います。

## 拡張機能のデバッグ
手っ取り早く拡張機能を動かすには、拡張機能のデバッグ機能を使います。
Firefoxのアドレスバーに、`about:debugging#/runtime/this-firefox`を打ち込み、デバッガを出します。
デバッガから、一時的な拡張機能 → 一時的なアドオンを読み込む を選び、ファイル選択ダイアログを出します。
ファイル選択ダイアログで、先ほど入手したmenu-demoフォルダの「manifest.json」を選び実行します。

動作確認として、ブラウザの適当なところを右クリックしてみます。
うまく動作していれば、コンテキストメニューに、Menu demoと表示されています。
表示まで確認できたら、サンプルの中身を見ていきます。

# サンプルのファイルについて
サンプルのファイル構成は次のようになっています。

* _locales
* icons
* sidebar
* background.js
* manifest.json
* README.md

今回必要なのは、manifest.json、_locales、background.jsの3つだけです。
それぞれ何に使うのか下に書きます。

* manifest.json
    * 拡張機能のメタデータや、他のファイルの参照を記述するファイルです。
    * 今回はメタデータ（名前など）、パーミッションを指定するために使います。
* background.js
    * 拡張機能の処理を書くファイルです。
* _locales
    * 拡張機能を多言語に切り替えられるように、文字列を定義するフォルダです。
    * 多言語に対応しないのであれば不要です。

#マニフェストファイルの作成
`manifest.json`には、拡張機能の名前、説明などを書くことができます。
最も重要なのは`permissions`で、APIへのアクセス権を付与します。
今回は、コンテキストメニューの追加と、タブの生成を行うので、`menus`、`tabs`を指定します。
`icons`と`sidebar_action`は今回は使わないので、削除します。

```json:manifest.json
{
    "manifest_version": 2,
    "name": "__MSG_extensionName__",
    "description": "__MSG_extensionDescription__",
    "version": "1.0",
    "default_locale": "en",
    "browser_specific_settings": {
        "gecko": {
            "strict_min_version": "56.0a1"
        }
    },

    "background": {
        "scripts": ["background.js"]
    },
  
    "permissions": [
        "menus",
        "tabs"
    ]
}
```

# スクリプトの作成
それでは実際に、スクリプトを書いて動かしてみましょう。
サンプルのbackground.jsは、大きく分けて3つの部分からできています。

* ボタンのイベント登録
    * onCreated、onRemoved、onErrorが定義されている部分です。
    * サンプルではログへの書き込みしか行っていないので、今回は使いません。
* メニューボタンの追加
    * browser.menus.create()が呼ばれている部分です。
    * ボタンの文字や、ボタンの種類（チェックボックスやラジオボタン）の定義をしています。
* 処理部分
    * 上記以外の部分です。
    * メニューボタンを押した際のイベントや、クリック時の処理の実装をしています。

###メニューボタン
まずはコンテキストメニューにボタンを追加してみます。
browser.menus.create()が呼び出されているあたりを、以下に書き換えます。

```javascript:background.js
// 選択範囲翻訳
browser.menus.create({
    id: "translation-selection",
    title: "選択範囲を翻訳する",
    contexts: ["selection"],
}, onCreated);

// ページ翻訳
browser.menus.create({
    id: "translation-page",
    title: "このページを翻訳する",
    contexts: ["all"],
}, onCreated);
```

* `id`
    * ボタンのID。クリック時のイベント処理で使います。
* `title`
    * ボタンの文言。
* `contexts`
    * ボタンの表示される条件を指定します。[（詳細）](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus/ContextType )
    * allは常時表示されます。
    * selectionは選択しているものがある場合のみ表示されます。選択範囲翻訳は選択中のみ使用可能なのでこっち。

###処理の実装
次は、ボタンをクリックしたときの処理を実装していきます。

```javascript:background.js
browser.menus.onClicked.addListener((info, tab) => {
    var url = "";
    switch (info.menuItemId) {
        case "translation-selection":
            url = CreateTranslationURL(info.selectionText, null);
            break;
        case "translation-page":            
            url = CreateTranslationURL(null, info.pageUrl );
            break;
    }
    OpenTranslationText(url, tab.index + 1);             // 直後のタブに生成
});
```
`info.menuItemId`に押したボタンのIDが入っているので、switch文で各ボタンを押した際の処理に振り分けます。
今回は、ボタンに応じて、ページ翻訳 or 文章翻訳のURLを生成し、そのURLを開く処理を行っています。

URL生成はこんな感じ。
google翻訳のクエリストリングを参考に、無理やり作ってます。

```javascript:CreateTranslationURL
function CreateTranslationURL(text = null, targetUrl = null){
    var isTextTranslate = text != null;
    
    // URL生成
    var url = "https://translate.google.co.jp/";
    if (!isTextTranslate){
        // ページ翻訳の場合
        url += "translate";
    }
    
    // 共通部クエリ生成
    var query = "?";
    query += "sl=auto";                               //  翻訳元言語　：　自動検出
    query += "&tl=ja";                                   //  翻訳先言語　：　日本語
    
    // テキスト翻訳orページ翻訳で固有のクエリ
    if(isTextTranslate){
        query += "&op=translate";                          // テキスト翻訳
        query += "&text=" + text;                       //  翻訳対象テキスト
    }else{
        query += "&u=" + encodeURI(targetUrl);
    }
    url += query;
    
    return url;
}
```

最後にタブを開く部分の処理を実装します。
`tabs`のAPIを使って、URLを新しいタブで開きます。

```javascript:OpenTranslationText
function OpenTranslationText(url, index) {   
    browser.tabs.create({
        "url" :  url,
        "active" : true,
        "index" : index
    }).then(function(value) {
    // 非同期処理が成功した場合
        console.log('実行結果:' + value); 
    }).catch(function(value) {
        // 非同期処理が失敗した場合
        console.log('実行結果:' + value); 
    });
}
```

* `url`
    * タブのURLです。
* `active`
    * 開いたタブをアクティブにするか設定できます。
* `index`
    * タブを何番目に生成するか設定できます。現在のタブの直後に生成するため、`tab.index + 1`を呼び出し側で指定しています。

###動作確認
ここまで実装したら、動作確認してみます。[拡張機能のデバッグ](#拡張機能のデバッグ)と同様に実行します。
以下の項目ができればOKです。

* コンテキストメニューに「このページを翻訳する」が表示される
* 「このページを翻訳する」をクリックすると、ページ全体を翻訳したタブが開く
* 文字列を選択しているとき、コンテキストメニューに「選択範囲を翻訳する」が表示される
* 「選択範囲を翻訳する」をクリックすると、選択範囲の文字列を翻訳したタブが開く

動かない場合は、デバッガから原因を特定できます。[拡張機能のデバッグ](#拡張機能のデバッグ)を参照して、デバッガを開きます。
デバッガにはデバッグ中の拡張機能が表示されます。そこの調査ボタンを押すと、拡張機能用のデベロッパーツールが表示されるので、ブレークポイント等で、調査してみてください。

#多言語対応
[メニューボタン](#メニューボタン)では、言語を日本語で定義しました。このままでは、日本以外のユーザが使えないので、英語でも表示できるようにします。[（詳細）](https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions/Internationalization)


###スクリプトの多言語対応
拡張機能では、ブラウザの言語によって自動的に、対応する言語に文字列をを切り替える機能が付いています。
`_locales`フォルダは、各言語の文字列定義することができます。まずは日本語用の文字列を定義してみます。

`_locales/ja/messages.json`を作成し、以下のように文字列を定義します。

```json:_locales/ja/messages.json
{
  "menuItemTranslationSelection": {
    "message": "選択範囲を翻訳する",
    "description": "選択範囲の文章を、Google翻訳で翻訳します"
  },
  
    "menuItemTranslationPage": {
    "message": "このページを翻訳する",
    "description": "このページを、Google翻訳で翻訳します"
  }
}
```

同じように、英語の文字列を`_locales/en/messages.json`に定義します。

```json:_locales/en/messages.json
{
  "menuItemTranslationSelection": {
    "message": "Translate selected range ",
    "description": "Translate selected range with Google Translation"
  },
  
    "menuItemTranslationPage": {
    "message": "Translate this page",
    "description": "Translate this page with Google Translation"
  }
}
```
この文字列は、`browser.i18n.getMessage("menuItemTranslationSelection")`の形式で呼び出すことができます。
`background.js`の[メニューボタン](#メニューボタン)を、以下のように書き換えます。

```javascript:background.js
// 選択範囲翻訳
browser.menus.create({
    id: "translation-selection",
    title: browser.i18n.getMessage("menuItemTranslationSelection"),
    contexts: ["selection"],
}, onCreated);

// ページ翻訳
browser.menus.create({
    id: "translation-page",
    title: browser.i18n.getMessage("menuItemTranslationPage"),
    contexts: ["all"],
}, onCreated);
```
変更点は、`title`を文字列定義から呼び出すようにしただけです。
確認する場合は、ブラウザの言語を英語にすることで確認できます。
さらに他の言語を追加する際は、言語コードのフォルダと、`messages.json`を追加すればOKです。

###マニフェストファイルの多言語対応
`manifest.js`では、ブラウザAPIが使えないので、他の方法を使います。
`manifest.js`内で、`__MSG_XXXXXXX__`と記述すると、各言語の`messages.json`ファイルから、`XXXXXXX`キーと一致する文字列に置換します。

ですので、`manifest.js`と、`messages.json`ファイルを以下のように変更します。

```json:manifest.json（抜粋）
    "name": "__MSG_extensionName__",
    "description": "__MSG_extensionDescription__",
```

```javascript:_locales/ja/messages.json（抜粋）
  "extensionName": {
    "message": "Translation konjac",
    "description": ""
  },

  "extensionDescription": {
    "message": "Google翻訳へのリンクを追加する拡張機能です",
    "description": ""
  },
```
これで、マニフェストも他言語に対応させることができるようになります。

# DEMO
[Translation-konjac](https://github.com/tanntara/Translation-konjac)

# 参考URL
[ブラウザー拡張機能](https://developer.mozilla.org/ja/docs/Mozilla/Add-ons/WebExtensions )