非常に混み入ったデバッグの話

# はじめに
プラグインを作成していて、イベントリスナーが多岐にわたってくると、デバッグが大変になります。プラグインは主に2つのスクリプトファイルで作成しています。content.jsとbackground.jsです。今回は、popup.jsは使用しません。
cntent.jsはひとつのタブで動作します。今回は、動作させるタブは一つです。そのタブで二つのスクリプトファイルは動作しています。このプラグインはテスト用です。イベントリスナーの動きをテストするものです。
テストページは、youtubeの動画を3つ埋め込んでいます。ブログは、jimdofree.comを使用しています。無料のブログです。urlは、"https://tecoyan-net.jimdofree.com/2024/09/07/テスト"
です。このブログページを使用して各種イベントリスナーのテストを行います。
# プラグインのダウンロード
[https://favorite.tecoyan.net/slim/zip/JimdoRepeat.zip](https://favorite.tecoyan.net/slim/zip/JimdoRepeat.zip)
このプラグインは、未公開のため、ダウンロードボタンを用意しました。一般利用目的ではなく、デベロッパー向けのためです。
テストサイトを実行するには、事前に、プラグインをパソコンへインストールしておきます。chromeブラウザーの拡張機能ボタンでインストールできます。

# プラグインのソースコード
[https://github.com/tecoyan/jimdoRepeat](https://github.com/tecoyan/jimdoRepeat)
このGitHubサイトで、プラグインのソースコードを確認できます。



# テストの種類
(1)tabId取得
(2)frameIds取得
(3)webNavi
(4)hoverTrigger
(5)表示
(6)リセット
(7)ホバー
以上の7種類のテストを行います。テストの種類はボタンを表してます。

# (1)tabId取得
このボタンをクリックすると、ブログページのタブIdを取得します。ブログのページはjimdo編集画面で作成したものです。そのページに、自作のポップアップ画面をアペンドしています。このポップアップ画面に各種のテストボタンを配置しています。
ここで、そのポップアップ画面を表示します。
![tabId取得.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/e865c301-af6f-c965-c0b5-5768c095aae3.png)

画面の右側にそのポップ画面が表示されます。
テストは、この画面で行います。画面にはいくつかのボタンがあります。それらのボタンをクリックして、イベントリスナーのテストを行います。
ここで、話をもとに戻します。ここでは、tabId取得ボタンのテストを行います。ポップ画面の右上にtabId取得ボタンがあります。それをクリックすると今のアクティブタブのtabIdを表示します。
tabId 200899634のような形式で表示されます。この9桁の数字がtabIdです。background.jsでは、このtabIdを使用して数々のプラグインメソッドを実行します。例えば、
chrome.runtime.sendMessage({メッセージ},()=>{.....});のような形式で、メッセージを送信して、background.js側で処理を行うことができます。
# (2)から(7)は、作成中です。

# 対策
今回の問題は、sendResponseを返すところで、作成当初は、非同期、同期の理解があいまいだったため、sendResponseが思うように実行しなかった。試行錯誤を繰り返したが、うまく行かず、もう一度、原点に戻って非同期、同期の仕様をよく読みました。やはり、return trueの問題でした。それまでもreturn trueのことは何度もみてましたが、よく理解していませんでした。今回、ようやく理解できましたので、使用しています。こうすることで、sendResponseはタイミングよく実行できています。仕様書はよく読まないとダメですね。再認識しました。


# 実際のコード
``` content.js
//一部抜粋
/*########################################
*get_tabIdメッセージ送信 
* 
*########################################*/
//function handleError(error) {
//    console.log(`Error: ${error}`);
//}
//return new Promise(....の変形版
//[[PrimiseState]]確認のため
function sendMessage_tabId() {

    rs = new Promise(function (resolve) {
            //background.jsへ送信
            console.log("2. chrome.runtime.sendMessage({type: 'get_tabId'}を送信します。");
            chrome.runtime.sendMessage({type: "get_tabId"}, function (response) {
                console.log("6. backから応答が返りました。　"+response.tabId);
                resolve(response.tabId);
                console.log("7. resolveしました。　"+rs);

            });
            console.log("3. ここへは....");
        
    });
    //Promise(pending)を返す。
    console.log("4. ここは、いつ???");
    return rs;

}
/*##############イベントリスナー##################　
*$("#tabId").on('click' 
* 
*###############イベントリスナー##################*/
$("#tabId").on('click', (e) => {
    rs = "";
    console.log("1. sendMessage_tabId().thenをコール");
    sendMessage_tabId().then(response => {
        //応答
        //
        tabId = response;
        console.log("8. .then "+tabId);
        $("#popup").html("tabId " + response);
        $("#popup").css({"display": "block", "zoom": 2});

    });
    //
    
    console.log("5. 非同期確認 tabId取得 "+rs);
    console.log(rs);
});

```
``` background.js
//一部抜粋
/***********************************************
 *get_tabId
 * 
 *************************************************/
case "get_tabId":
console.log("■get_tabIdメッセージを受信 "+tabId);
//(async()=>{
    //このメソッドは、非同期なので、結果を待たずに、先にリターンする。
    //そのため、あとからのsendResponse送信を有効にするため、return trueしている。
    chrome.tabs.query({},tabs=>{

                for (let i = 0; i < tabs.length; i++) {
                    if (tabs[i].url.indexOf("https://tecoyan-net.jimdofree.com/") !== -1) {
                        active_tabId = tabs[i].id;

                        tabId = tabs[i].id;
                        console.log("ここで、tabIdをセット　"+tabId);
                        break;
                    }
                    sleep(0.5);
                }
                console.log("tabId ここで応答 "+tabId);
//ここでは、NG、このsendResponseは、参照できないか???
//応答よりも、ここで、送信元へメッセージ送信で送った方がベターか?
//chrome.tabs.sendMessage(tabId,{type:"set_tabId",content:tabId}, function(response) {
//           });
//ここでの応答を有効にするため、return trueをあらかじめ、返している。
                sendResponse({tabId:tabId});


    });
    //これが必要
    //これは、リスナーがリターンした後でも、sendResponseへの参照が有効になる設定。
    //非同期応答を有効にしている。
    return true;

break;
```
``` background.js
/***************************************************************
 *clickSetメッセージ 
 *すべての動画フレームに対して、再生/一時停止ボタン要素のclickリスナーを登録 
 * パラメータはなし。
 * どこで、sendResponse()で応答を返すか?????
 * 
 *****************************************************************/
case "clickSet":
        console.log("clickSetメッセージを受信しました。　");
        extId = chrome.runtime.id;
        /*******************************************
         *タブはアクティブタブのみが対象 
         *
         *******************************************/
        chrome.tabs.query({}, tabs => {
                for  (const tab of tabs) {
                    /***********************************
                     *iframeプレーヤーチェック
                     *ここで、今開いているタブのみにする　アクティブのタブ  
                     ***********************************/
                    if (active_tabId === tab.id) {
                        console.log("Msg■clickSet  for ループ　" + tab.id);
                        tabId = tab.id;
                        try {
                                //urlをチェック                    
                                if (tab.url.indexOf("https://tecoyan-net.jimdofree.com/") !== -1) {
                                    //ここで、タブの全フレームデータを取得して保存
                                    //chrome.webNavigation.getAllFrames({tabId: tabId}).then(logFrameInfo1, onError);
                                    chrome.webNavigation.getAllFrames({'tabId': tabId},framesInfo=>{
                                        frm_arr = [];
                                        for(const frm of framesInfo) {
                                            console.log("Msg■clickSet  frm.frameId　= " + frm.frameId);
                                            frm_arr.push(frm.frameId);

                                            try {
                                                /********************************
                                                 *youtube iframeプレーヤーチェック
                                                 *今開いているタブのみにする　アクティブのタブ
                                                 *frameType sub_frameのみ
                                                 *    
                                                 **********************************/
                                                if (frm.frameType === "sub_frame") {
                                                    console.log("Msg■clickSet  フレームInfo url = " + frm.url + "  " + frm.frameId);
                                                    //executeScript()を実行
                                                    //filesとfuncを同時に指定できないので分けて出す。
                                                    chrome.scripting.executeScript({
                                                        target: {tabId: tabId, frameIds: [frm.frameId]},

                                                        files: ["jquery.js"]
                                                    }).then(() => {
                                                        //ここでもexecuteScript()を出す
                                                        chrome.scripting.executeScript({
                                                            //ターゲットのフレームIdのDOMへアクセス  
                                                            target: {tabId: tabId, frameIds: [frm.frameId]},
                                                            /***********************************
                                                             *hoverメッセージで渡すパラメーター
                                                             * frm.frameId,
                                                             * extId,
                                                             * frm_arr,
                                                             * tabId,
                                                             * frm.frameId
                                                             * 
                                                             **************************************/
                                                            args: [frm.frameId, extId, frm_arr, tabId, frm.frameId],
                                                            //実行コード
                                                            func: (frameId, extId, frm_arr, tabId, ownId) => {
                                                                /*************************************
                                                                 *clickSetメッセージ
                                                                 *ここからが、executeScript()の処理関数
                                                                 *特定のtabIdの特定のframeIdで動作
                                                                 **************************************/
                                                                console.log("Msg■clickSet  このフレーム(" + frameId + ")のbody要素にclickリスナーを登録します。　" + frameId);
                                                                $("body").on('click', (e) => {
                                                                    let aa = document.querySelector(".ytp-play-button");
                                                                    let tt;
                                                                    /*********************************************
                                                                    *altKey押し下げは、一回目は不安定なため、続けて2回目は
                                                                    正しい結果になる。原因は不明。
                                                                    *******************************************/
                                                                    if (e.altKey === true) {
                                                                        alert("altKeyが押されました。 " + frameId + "  " + document.title + "  " + aa.ariaLabel);
                                                                        //このdocumentは、altKeyを押している動画のドキュメント
                                                                        if (aa.title !== "") {
                                                                            tt = aa.title;
                                                                        }
                                                                        else {
                                                                            tt = aa.ariaLabel;
                                                                        }
                                                                        console.log("Msg■clickSet  altKey クリック時のタイトル　" + document.title + "　フレーム(" + frameId + ")　" + tt);
                                                                        /***********************************************
                                                                         *ここで、'altTrigger'メッセージを送信
                                                                         *popパネルにボタン情報を表示
                                                                         *ariaLabelの表示内容が安定しない??? 
                                                                         *再生/一時停止ボタンのariaLabelの内容とボタンのホバー時の内容が一致しない???                                                                           * 
                                                                         *************************************************/
                                                                        //alert("Altキー　押し下げ　"+ownId);
                                                                        let dd = {type: 'altTrigger', frm_arr: frm_arr, tabId: tabId, ownId: ownId, title: document.title, ariaLabel: tt};
                                                                        chrome.runtime.sendMessage(extId, dd, function (msg) {
                                                                            //応答は? 特になし。     
                                                                        });
                                                                    }
                                                                });
                                                                /*************************************************
                                                                 *ホバーした時のtitle属性をとれるか
                                                                 * 
                                                                 *************************************************/
                                                                /*イベントリスナー登録*/
                                                                $(document).on({
                                                                    "mouseenter": function (e) {
                                                                        //alert("マウスエンター");
                                                                        //ここで、title属性を取得
                                                                        let aa = document.querySelector(".ytp-play-button");
                                                                        if (aa.title === "") {
                                                                            /*************************************************
                                                                            *以下が表示されないのは、フレームIdが0ではないため。 
                                                                            *この要素は動画の外側にあるため。 
                                                                            *alert()文は表示される。
                                                                            ************************************/
                                                                            chrome.runtime.sendMessage(extId, {type: "hover", ariaLabel: aa.ariaLabel, frameId: frameId, title: document.title, tabId: tabId}, function (msg) {
                                                                                //応答は? 特になし。
                                                                                console.log(msg)
                                                                                //alert(msg);
                                                                            });
                                                                        }
                                                                        else {
                                                                            //alert(aa.title);
                                                                            chrome.runtime.sendMessage(extId, {type: "hover", ariaLabel: aa.title, frameId: frameId, title: document.title, tabId: tabId}, function (msg) {
                                                                                //応答は? 特になし。
                                                                                console.log(msg);
                                                                                //alert(msg);
                                                                            });
                                                                        }
                                                                    },
                                                                    "mouseleave": function (e) {
                                                                    }
                                                                }, ".ytp-play-button");
                                                                /******************************************************
                                                                *再生/一時停止ボタンをクリック
                                                                *ここで、今の動画情報を更新する。
                                                                *  
                                                                *******************************************************/
                                                                $(".ytp-play-button").on('click', () => {
                                                                    alert("再生ボタンがクリックされました。");
                                                                });
                                                            }       //end func() executeScript()
                                                        });         //end executeScript()
                                                    });
                                                    console.log("sendResponseを返します。");
                                                    //最後で返したいが、動画の数でチェックするか?
                                                    //sub_frameの最後を見たいが
                                                    sendResponse({sts:"completed"});
                                                    //end .then(()=>{ 
                                                    //sleep(0.5);
                                                }
                                                else {

                                                }
                                            }   //try()
                                            catch (e) {

                                            }

                                        }       // end for()
                                    })();       //end 
                                    console.log("Msg■clickSet  getAllFrames()コール後　" + tabId);
                                    // sleep(0.5);
                                }else {

                                }
                        }catch (e) {
                            //なし
                        }
                    }
                }
        });
        return true;
break;    
```

最初にtabId取得ボタンをクリックすると、$("#tabId").on('click',と書かれているコードが実行します。
このコードがイベントリスナーです。ここで、sendMessage_sample();がコールされます。

# 調べたかったことは
#### 同期時間の確認
今回、調べたかったことは、同期型のメッセージ送信で、content.jsからbackground.jsへメッセージを送信し、その応答が正しい値で、background.jsからcontent.jsへ返されているのかということです。
そこで、console.log()を各所に入れて、それぞれの時間を表示して確認しました。
結果は想定通りの時間経過が示されていました。
その証拠となる表示画面を以下に示します。
この画面は、content.jsで、get_tabIdメッセージをbackground.jsへ送信し、その応答を受けとった時間を示しています。
17:15:02.819 get_tabIdメッセージをbackground.jsへ送信した時間
17:15:02.824 その応答をbackground.jsから受け取った時間

![矢印content.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/fbbffe3a-1a17-b2b3-573e-d9b38e71c54a.png)

この画面は、content.jsからget_tabIdメッセージをbackground.jsが受信した時間です。
17:15:02:822 get_tabIdメッセージを受信した時間
17:15:02:824 その応答を返した時間

![矢印background.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/19aaac7b-61a0-9bc9-ba09-a3e9edae3453.png)

それらの時間を整理すると、
content.js                 background.js
17:15:02.819     ----->    17:15:02.822  get_tabId受信
17:02:15.824     <-----    17:15:02.824　get_tabId応答

のような時間経過がわかります。
ここで、content.js側では、get_tabIdメッセージを送信してからその応答を受け取るまで、同期して、待っているのがわかります。

# このテストサイト
[テストサイト](https://tecoyan-net.jimdofree.com/2024/09/07/%E3%83%86%E3%82%B9%E3%83%88/)

このページでは、ctrlキーを押しながらclickすると薄色のピンク画面がポップアップします。そこのボタンで各種テストができます。
コンソールデバッガーをへ併用しながら使用します。3つの動画があります。それらの動画の情報を表示するために、さまざまなメッセージ送信をして表示しています。

# データの見える化について
ページに表示される画面のデバッグの時に、同時にデバッグコンソールで表示される変数やオブジェクトを見ることができると助かると思い、今回、ポップアップで表示しました。画面操作段階で、その場で同時にみれるため、デバッグ・コンソールを開かなくても必要なデータを見ることができます。

![見える化.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/cdfbaf00-7c8c-0f9a-e7e4-dea51ed99a63.png)
プログラム内部で使用している変数やオブジェクトデータをページ画面を操作している段階で直接確認することができます。フロントエンドデバッガーのようなものです。



# 考察
プラグインを作成していて、この同期型でメッセージ通信するケースは多々あります。必要とするデータがそろってから処理するために、よく使用しています。content.js側からbackground.jsへ必要とするデータを要求する場合は、このメッセージ送信で行います。今回の例はtabIdを取得する例です。content.jsでは自らtabIdを知ることはできないため、background.jsへ依頼して取得します。
### tabId取得ボタン
![tabId取得.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/e65a07c8-56f2-5121-3410-ebfe653fffc3.png)
この画面は、tabIdボタンをクリックしたときの画面です。ボタンの下にタブIdを表示しています。他のボタンをクリックする前に、先にこのボタンでtabIdをスクリプト変数に保存しておきます。
![tabId取得コンソール.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/51c80505-3d25-75a0-9e09-a45d5856176d.png)
画面右側のスクリプトを見ると、tabId:200901345と表示されています。sample.content.jsでは、以後、このtabId変数を参照します。
### Promiseオブジェクト
(1)pending

![promise(pending).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/a3b9415e-ce9e-8213-0945-071c2a7e9cc7.png)
この画面は、tabIdのメッセージをbackground.jsへ送信後、すぐにリターンしてきたときのPromiseオブジェクトの状態です。まだ、pending状態を表しています。

(2)fulfilled

![promise(fulfilled).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/e006d002-a61e-b67b-df93-72ed53afce95.png)
この画面は、tabIdメッセージの応答が来た時のPromiseオブジェクトの状態です。resolve()後に、fulfilled状態になっています。更に、tabIdの結果が[[PromiseResult]]プロパティにセットされています。
この結果は、非同期の動作になっています。content.jsからtabIdメッセージをbackground.jsへ送信し、応答を待たずにすぐにリターンしています。応答は、background.js側でchrome.tabs.query(...)を実行後に、sendResponse()で、content.js側に返しています。

(3)時系列の表示
content.js側で、tabIdメッセージ送信し、すぐにリターンして、後から応答が返ってくるまでを表示しています。
![promise(最終結果).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/8f959cb3-6b56-24d8-4fea-02405b6c758a.png)
この画面のPromise iのところをホバーすると、メッセージが表示されます。
「この値は最初の展開時に評価時されました。その後、変更されている可能性があります。」とあります。これは、展開時には、pendingになっています。そのご、resoleveでfulfilledになっています。
# 進行順表示(console.log)
この表示は、tabId取得ボタンをclickした時のcontent.jsでのconsole.logを表示したものです。番号を振って処理の流れ順に表示しています。右側にはコードの行番号が処理順に表示されています。
コードの進行は必ずしも上から下へは流れてはいません。中央の番号の順に処理は流れています。これを見ると、tabIdボタンのクリック後、に1.sendMessage_tabId.then()のコードが実行され、以後、番号順に進行し、最終的に、応答が返って、tabIdが取得される経緯を表示しています。

![進行順表示.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/198006/842d7814-bfd1-dfff-72e7-ca4b49273925.png)
上述の「実際のコード」のcontent.jsに、このコードを示しています。
02:37:51.469秒に、sendMessage_tabId()がコールされ、02:37:51.473秒には、応答が返っていることがわかります。
このように、コードの記述順通りには処理は実行されてはいません。非同期処理のため、かなり、コードを前後して進行しています。これが、「非常に混み入ったデバッグの話」です。


# あとがき
まだ、完成していませんが、途中経過を示しながら、今後、さらに、内容を整理して行きたいと思っています。今回、手こずったことは、プラグインのメッセージ通信で応答を返すsendResponseがすぐにリターンしてきて、正しい応答結果が取得できなかったことです。何度も試行錯誤をつづけましたが、うまく行かず、一時は断念しようとまで思いました。しかし、粘り強く、続けて、最終的には、return true;にたどり着き、無事解決しました。今は満足しています。