🖥

Chrome拡張機能の概要から公開まで(ManifestV3対応) ~開発編~

2023/06/10に公開

はじめに

ManifestV3でChrome拡張機能を公開したので公開までの流れを記事にしました。
本記事は開発編です。関連記事はこちら。
https://zenn.dev/kumomomo/articles/c4b5b363cab8f0
https://zenn.dev/kumomomo/articles/faf1842a3d4c2a

にじさんじオフィシャルストアを使いやすくする拡張機能を作成しました。
https://chrome.google.com/webstore/detail/easy-nijisanji-store/ggjpbhbacldfgbbmdoccikkjhemeapoc

本記事では開発で使ったAPIや小ネタを解説します。ここで解説するものは作る拡張機能次第では関係ない部分も多いかもしれません。

デバック方法

公開する場合はフォルダを圧縮する必要がありますが、デバックする場合はフォルダを指定することで拡張機能を動作させることができます。拡張機能の管理ページから"パッケージ化されていない拡張機能を読み込む"からプロジェクトフォルダを選択することで拡張機能を読み込むことができます。拡張機能のファイルを更新した場合は拡張機能の再読み込みボタンを押すことで更新が反映されます。なお、Content Scriptsを更新した場合は該当のwebページも再読み込みしてください。

パッケージ化していない拡張機能を読み込む

Message Passing

概要編でも紹介しましたが、具体的に送信側と受信側に実装について解説します。
送信側はtabs.sendMessageruntime.sendMessageを使います。
tabs.sendMessageはメッセージの中身と送信するタブIDを引数に取ります。タブ情報はchrome.tabs.query()で取得できます。いつからかAPIがPromise返すようになりました(ManifestV3から?)。まだ対応していないAPIもあるので公式リファレンスを確認して下さい。
responseにはonMessageで返された値が入ります。

sender.js
//runtime.sendMessageの場合
const response = await chrome.runtime.sendMessage({"message":"hogehoge"});

//tabs.sendMessageの場合
chrome.tabs.query({}).then((result) => {
    result.forEach((tab) => {
    if (tab.url.match(/https:\/\/hogehoge.jp\/.*/)) {
        const response = await chrome.tabs.sendMessage(tab.id, {"message":"hogehoge"});
    }
    });
});

受信側はchrome.runtime.onMessage.addListener()でListenします。
ここでメッセージを受け取った処理を行い、sendResponse()でレスポンスを返します。
このときtrueを返されないとsendMessageでレスポンスを受け取れません(1敗)。
https://zwzw.hatenablog.com/entry/2019/12/04/200000

receiver.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.type == 'open') {
    //メッセージを受け取った処理
    chrome.tabs.create({ url: 'mylibrary.html' }).then((response) => {
      sendResponse({ result: 'success', tab: response.id });
    });
  }
  return true; //trueを返す。
});

データの保存

拡張機能を作ると設定項目やユーザーデータを保存したいケースが出てくると思います。
chrome.storageでユーザーデータの保存ができます。保存場所を用途に合わせて選んでください。保存できる容量にも制限があるのでchrome.storage.localが無難そう。

API 特徴
storage.local ローカルに保存されます。容量は5MBまで。unlimitedStorageをリクエストすると容量を拡張できます。
storage.sync Googleアカウントに同期した領域にデータが保存できます。容量は100kBまで。
storage.session ブラウザのセッション中に有効なデータ領域です。容量は10MBまで。

これらはmanifest.jsonのpermissionsstorageを指定することで使うことができます。

  "permissions": ["storage"],

storageにはkey-value方式でデータを保存します。

storage.js
    //get keyを指定してvalueを取得 
    chrome.storage.local
      .get("theme")
      .then((value) => {
        //データ取得した処理
      });

    //set key-valueのオブジェクトでデータを保存
    chrome.storage.local
      .set({ "theme": "dark" })
      .then(() => {
        console.log("Value is set");
      });

cookieへのアクセス

拡張機能によってはcookieにアクセスしたいケースもあるかと思います。(ユーザ操作に変わって通信したいときとか)
manifest.jsonにpermissionshost_permissionsを記述することで使用することができます。
私はcookieの取得しかしませんでしたが、設定も可能です。
https://developer.chrome.com/docs/extensions/reference/cookies/

  "permissions": ["cookies"],
  "host_permissions": ["*://hoge.jp/"],

getAllhost_permissionsで指定したホストのcookieをすべて取得できます。

    const cookies = await chrome.cookies.getAll({});

通信の監視

ユーザ操作のリクエストの中身を取得して拡張機能で処理したいユースーケースがあり通信の監視方法を調べました。

ManifestV2まではwebRequestAPIでリクエストの中身を見たり、通信をリダイレクトできたりしましたがManifestV3で廃止されました。
かわりにchrome.declarativeNetRequestで静的パターンマッチしたリクエストのリダイレクトなどができるようになりました。
chrome.declarativeNetRequestで通信の監視ができなそうだったので他の方法で実装しました。

色々調べましたが下記リンクが実装方法含めまとまっていたのでこれを参考にしています。
https://medium.com/@ddamico.125/intercepting-network-response-body-with-a-chrome-extension-b5b9f2ef9466

実装方法としてはXMLHttpRequestにモンキーパッチを適用してXMLLHttpRequest.prototype.openXMLHttpRequest.sendでリクエストのpayloadとresponseを取得します。

injectedScript.js
// define monkey patch function
const monkeyPatch = () => {
  let oldXHROpen = window.XMLHttpRequest.prototype.open;
  window.XMLHttpRequest.prototype.open = function () {
    this.addEventListener('load', function () {
      const responseBody = this.responseText;
      document.getElementById('myDataHolder').setAttribute('response', responseBody);
    });
    return oldXHROpen.apply(this, arguments);
  };

  let oldXHRsend = window.XMLHttpRequest.prototype.send;
  window.XMLHttpRequest.prototype.send = function (postData) {
    document.getElementById('myDataHolder').setAttribute('payload', postData);
    return oldXHRsend.apply(this, arguments);
  };
};
monkeyPatch();

補足としてinjectedScriptを元リンクではインラインスクリプトでwebページに挿入していますが、ManifestV3ではインラインスクリプトが禁止されています。そのため別途javascrptファイルを挿入する実装に変更しました。それに伴いmanifest.jsonに下記記述をする必要があります。

inject.js
const injectScript = () => {
  var script = document.createElement('script');
  script.setAttribute('src', chrome.runtime.getURL('injectedScript.js'));
  (document.head || document.documentElement).appendChild(script);
  script.remove();
};
manifest.json
  "web_accessible_resources": [
    {
      "resources": ["injectedScript.js"],
      "matches": ["https://shop.nijisanji.jp/*"]
    }
  ],

外部ファイルからwebページのリソースにアクセスするためにweb_accessible_resoucesでリソースとwebページを指定します。

GitHubで編集を提案

Discussion