⚙️

chrome.devtools.network を使って、拡張機能からネットワークリクエストを取得する

2023/11/15に公開

完成図イメージ

Twitterでハッシュタグをつけるとアイコンが表示されるときがあります。企業がキャンペーンをしてるときや #MeToo など社会運動があるときに使われます。なんというタグを使うとアイコン付きのハッシュタグが表示されるかは一般に公開されていませんが、chrome.devtools.networkAPIを使って一覧を表示する拡張機能を作っていきましょう。

拡張機能を使えばWebページに表示されている多くの情報にアクセスできるのはこれまでの拡張機能の開発で実感していただけたかと思います。しかし拡張機能で全ての情報が簡単に取得できるわけではありません。例えばネットワークリクエストにアクセスするにはある制限がかかっています。ネットワークリクエストはDevToolsからネットワークタブを開いて閲覧することができる情報の一覧です。

ネットワークリクエストにアクセスできると、Cookieやhiddenで埋め込まれている情報など、画面上に表示されている以上の情報にアクセスすることができます。それゆえセキュリティーを重視するChromeでは、拡張機能開発者がネットワークリクエストに気軽にアクセスできないような仕組みになっています。それを裏付けるものとして、ネットワークリクエストにアクセスするためのchrome.devtools.networkAPIはユーザー自身がDevtoolsを開かないと利用することができません。さらにDevToolsを自動で開くようなAPIは提供されていません。開発者でもない限り、DevToolsを開いたことがないユーザーがほとんどでしょう。それゆえchrome.devtools.networkAPIは開発者にとって魅力的なAPIとはいえ、利用してくれるユーザーは限られてくると思います。

ファイルの作成

まずは必要なファイルを作っていきましょう。

manifest.json
{
  "manifest_version": 3,
  "name": "Hashtag Viewer",
  "version": "1.0.0",
  "description": "Twitterのハッシュタグの一覧を表示します",
  "devtools_page": "devtools.html" // 👈①
}
devtools.html
<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <script src="devtools.js"></script> // 👈②
</body>
</html>
devtools.js
chrome.devtools.panels.create( // 👈③
  "HashTags", // パネルの名前
  "images/logo-retweet.png", // アイコン画像を指定できる
  "./panel.html", // パネルで表示されるhtml
  () => {} // callback
);
panel.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hashtag Viewer</title>
</head>
<body>
  <h1>ハッシュタグ一覧</h1>
  <script src="/panel.js"></script>
</body>
</html>
panel.js
chrome.devtools.network.onRequestFinished.addListener((req) => { //👈④
  console.log(req); //👈⑤
});

①DevTools のパネルを作るのに特別な permission はいらない

DevToolsでカスタムパネルを追加するには、manifest.jsondevtools_page項目にhtmlファイルを追加するだけです。このあたりはポップアップやオプションページと扱いが似ています。いずれも独立したWebページだからでしょうか。

devtools.htmlの役割

devtools.htmlは非常に簡潔なものになります。このファイルはdevtools.jsを呼び出すためにあるようなものです。ユーザーがDevToolsを開くとmanifest.jsonで指定したdevtools.htmlがロードされ、devtools.html内の<script>で読み込まれているdevtools.jsがロードされます。このhtmlh1spanなど見た目に関する記述を書いても画面上のどこにも反映されません。また、Chrome拡張機能はコンテンツセキュリティポリシーに従う必要があるため、JavaScriptのコードはインラインで記述してはならず、scrptタグで読み込む必要があります。

chrome.devtools.panels.create()

パネル内ではchrome.devtoolsAPIとchrome.extensionAPIしか使うことができません。しかし、メッセージを送ることでバックグラウンドで処理した結果を受け取ることができるのでそれほどの不便はありません。

chrome.devtools.panels.create()の構文

chrome.devtools.panels.create(title, iconPath, htmlPath, callback)
引数 説明
title string パネルに表示される名前
iconPath string パネルに表示されるアイコンのパス
pagePath string パネルのhtmlのパス
callbacks? string コールバック

このpagePathにはhtmlファイルを指定しなければなりません。パネルはこの関数を使って作ります。
devtools.jsも補助的な役割のファイルで、実際にパネルに表示されるファイルはpanel.htmlで、パネルに関する処理を書くファイルはpanel.jsです。

https://developer.chrome.com/docs/extensions/mv3/devtools/

chrome.devtools.network

冒頭でも説明したように、chrome.devtools.networkAPIを使うことでネットワークリクエストの情報を取得できるようになります。ネットワークリクエストの一覧の情報はHARファイルという情報にまとめられます。HTTP Archive ファイルの略で HARファイルと呼ばれています。HARファイルはJSON形式のファイルで、HTTP通信の内容に加えてページのロード時間なども含まれます。HARファイルはchrome.devtools.network.getHAR()メソッドを使うことで取得できます。

しかし今回は通信内容のファイルが保存したいわけではなく、やり取りされるネットワークリクエストにアクセスし、必要な情報だけを入手するだけなのでchrome.devtools.network.onRequestFinishedイベントに、イベントハンドラーを追加して、必要な情報だけを取得するだけの処理になります。

onRequestFinishedの構文

chrome.devtools.network.onRequestFinished.addListener((req) => {
    ...
})

callbackの引数

パラメーター Head
req request HARファイルと同じもの。script、jSON、画像などが含まれる。

https://developer.chrome.com/docs/extensions/reference/devtools_network/#type-Request

⑤ ネットワークリクエストを出力してみる

④で追加したイベントハンドラーの引数にはreqを指定しました。この変数にはネットワークリクエストの全ての情報が入っています。どのような情報が入っているかコンソールパネルを開いて確認してみましょう。

しかし今回のようにDevToolsのパネルの拡張機能の開発をしているとき、コンソールパネルを開くのに一手間かかります。以下の手順を踏んでください。

  1. twitterを開いているページでDevToolsを開く(右クリック->検証)
  2. パネルの左端の >> と書かれてある箇所をクリック
  3. HashTags をクリック
  4. HashTags パネル上でさらにDevToolsを開く(右クリック->検証)
  5. コンソールパネルをクリック



上の手順を踏むと、コンソールには大量の Object が出力されていることが確認できます。これらのオブジェクトは全て、ネットワークパネルで受けとったネットワークリクエストと等しいものです。この中から作りたい拡張機能に合わせてリクエストを選別を行います。しかしconsole.logでリクエストの一覧を出力したものはとても見づらいので、必要なネットワークリクエストを選別するときは普通、ネットワークパネルから確認します。

パネルを先頭に移動する

HashTagsパネルは常にパネル一覧の最後に配置されるので開発中は先頭に移動させておくといいです。HashTagsパネルをドラッグ&ドロップして要素の左側においておくと便利です。しかし先頭に配置しても、DevToolsを開いたときにデフォルトで開かれるパネルは要素パネルであることに注意してください。


パネルの順番は変えられる

ネットワークタブで確認する

必要なネットワークリクエストを選別するために、ネットワークパネルを利用するのがいいことを説明しました。Twitterから、DevToolsを開いてネットワークパネルを開き、Twitterを再度読み込みしてみます。するとこのように多くのデータが送られてくることがわかります。ネットワークリクエストの多くは用途不明なJSファイルだったり、ユーザーのアイコンだったりするので面白いものはほとんどありません。

ここで面白そうなファイルを見つけるコツを紹介します。それは表示ファイルをFetch/XHRをクリックしてフィルタリングすることです。このフィルタリングを設定して表示されるファイルは、ユーザーの投稿などデータベースから取得してきた情報が格納されていることが多いです。

Fetch/XHR をクリックしてフィルタリング

こうして表示されたファイルからhashflag.jsonというファイルをクリックしてください。すると右にサイドバーが出てくるので「プレビュー」をクリックします。

このプレビューで表示されるものはhashflag.jsonの内容そのものです。もっといえば、ハッシュタグに関数情報が格納されているjsonファイルです。ブラウザで見やすくするため整形されていますが、これらのデータは1つのjsonファイルに格納されています。

この中の[0..99]と書かれている箇所をクリックします。するとデータが展開されてhashtagとかかれた項目が見えます。

この中に100WINと書かれたものがあるので、twitterを開き、#100WINと入力してみましょう。すると絵文字つきハッシュタグが表示されます。

他に表示されているハッシュタグを追加しても同じように絵文字付きハッシュタグが表示されます。

JSONの中身を見ると次のようなオブジェクトの構造になっていることがわかります。

{
    "hashtag": "100WIN",
    "starting_timestamp_ms": 1674720000000,
    "ending_timestamp_ms": 1704111600000,
    "asset_url": "https://abs.twimg.com/hashflags/LCSFranchise_LeagueEmoji_100Thieves2023/LCSFranchise_LeagueEmoji_100Thieves2023.png",
    "is_hashfetti_enabled": false
}

asset_urlに格納されているURLをに移動してみると、hashtagキーの文字を入力したときに出てきたのと同じ画像が出てきました。

抽出する情報はこれで決まりました。ネットワークリクエストからhashflag.jsonという名前のファイルのみを取得し、そのJSONからhashtagキーとasset_urlキーの一覧を表示してみましょう。

リクエストを抽出する

再びHashTagsパネルに戻ってネットワークリクエストがどのような形式のオブジェクトなのかを確認してみましょう。数ある中からhashflags.jsonファイルを選び取るには何かしらの条件分岐が必要です。そのためにネットワークリクエストの構造を知る必要があります。ネットワークリクエストは以下のような構造です。主要な情報はrequestキーかresponseキーの中にあることがわかります。

urlでフィルタリングする

コードに戻ります。ネットワークリクエストからURLを出力させてみましょう。先ほどの画像をみるとURLはrequestキーの中のurlキーに格納されていることがわかります。そこで、コードを次のように書き換えます。

panel.js
chrome.devtools.network.onRequestFinished.addListener((req) => {
- console.log(req);
+ console.log(req.request.url)
});

HashTagsパネルのDevToolsを開き、Twitterを再度読み込みをしてみます。するとconsole.log(req)のときと違って、ネットワークリクエストのURLのみが出力されました。

WindowsならControl + F、Macなら⌘ + Fを押してhashflagsと検索してみましょう。するとhttps://twitter.com/i/api/1.1/hashflags.json
というURLがヒットしました。

次にこのURLに着目してネットワークリクエストがどのようなものなのか調べてみます。

panel.js
chrome.devtools.network.onRequestFinished.addListener((req) => {
- console.log(req.request.url)
+ const url = req.request.url
+ if (url === 'https://twitter.com/i/api/1.1/hashflags.json') {
+   console.log(req);
+ }
});

出力されたオブジェクトを見ても先ほどは見れたハッシュタグのURLは見当たりません。コールバックで取り出したreqからネットワークリクエストのデータの本体(body)を取り出すには次のようなコードにします。

panel.js
chrome.devtools.network.onRequestFinished.addListener((req) => {
  const url = req.request.url
  if (url === 'https://twitter.com/i/api/1.1/hashflags.json') {
-   console.log(req);   
+   req.getContent((content, encoding) => {
+     const json = JSON.parse(content);
+     console.log(json);
+   });
  }
});

データの本体を取り出すにはgetContent()メソッドを使います。getContent()メソッドの返り値にデータの本体が格納されています。今回はJSONデータを扱うことがわかっているので、JSON.parse()を使ってJavaScriptで扱いやすくしています。

getContent()の構文

reqest.getContent((content, encoding) => {
}
パラメーター 説明
content string ネットワーク本体の情報
encoding string エンコーディング名。何もされてなければ空白。

最後にこれらの情報をHashTagsパネルに表示してみましょう。

panel.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Hashtag Viewer</title>
</head>
<body>
  <h1>ハッシュタグ一覧</h1>
+    <table id="dataTable">
+        <tr>
+            <th>ハッシュタグ</th>
+            <th>画像</th>
+            <th>URL</th>
+        </tr>
+    </table>
  <script src="/panel.js"></script>
</body>
</html>
panel.js
chrome.devtools.network.onRequestFinished.addListener((req) => {
  const url = req.request.url
  if (url === 'https://twitter.com/i/api/1.1/hashflags.json') {
    req.getContent((content, _) => {
      const json = JSON.parse(content);

+     const table = document.getElementById("dataTable");
+      
+    json.forEach((item) => {
+      const row = table.insertRow();
+      const cell1 = row.insertCell();
+      const cell2 = row.insertCell();
+      const cell3 = row.insertCell();
+
+      cell1.innerHTML = item.hashtag;
+      cell2.innerHTML = `<img src="${item.asset_url}" alt="画像" style="width:50px;height:auto;">`;
+      cell3.innerHTML = item.asset_url;
+     });     
    });
  }
});

次のように一覧が表示されると完成です。

パネルのデバッグをする

パネルを開く前に受信したネットワークリクエストはパネルに受信されない。そのため再度読み込みが必要

? アイコンの設定の仕方がわからない。サイズの問題?パスの問題?

document.location.hrefでは閲覧中のURLを取得できない

拡張機能のややこしいところはWebサイト、バックグラウンド、ポップアップが3つの世界でそれぞれ独立して動いているということです。今回使っているDevToolsのパネルも例外ではなくこの3つから独立して動いています。独立していることの何が厄介かというと思った通りの挙動をしないということです。たとえば現在開いているサイトのURLを取得しようとしてdocument.location.hrefを使っても、得られるものはサイトのURLではなくパネルのURLです。

DevTools 周りのAPI
https://developer.chrome.com/docs/extensions/mv3/devtools/

Discussion