【Chrome拡張機能】Google Colaboratoryの実行状況把握

# 作成したもの

「ランタイムの全ての実行」クリックしたときにメニューに
「実行中」、「実行完了」、「エラー発生」の3パターンが表示されるようにしました。

![2020-12-13_11h37_35.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428239/e3d35f8f-b72b-9aa7-2f4c-a06240e5fe57.gif)


# 作成した背景

画面外で
・実行中なのか？
・実行完了したのか？
・エラーが起きたのか？
の見方がわからないという問い合わせをたくさんいただいたので、
その質問を減らすために作成しました。
(把握の仕方のドキュメントは作成したのですが…)


# フォルダ構造


```:フォルダ
フォルダ
  ├ manifest.json
  ├ icon.png
  ├ content.js
  └ jquery-3.5.1.min.js
```

# manifest.json


```:manifest.json
{
  "manifest_version": 2,
  "name": "check exexution status of Google Colaboratory",
  "version": "1.0",
  "description": "check exexution status of Google Colaboratory",
 
  "content_scripts": [
    {
      "matches": ["https://colab.research.google.com/*"],
      "js": ["jquery-3.5.1.min.js", "content.js"],
      "run_at": "document_end",
      "all_frames": true
    }
  ],
  "icons": {
     "48": "icon.png"
  },
    "permissions": [
     "storage"
    ]
}
```


`all_frames: true` に関してはなくても動きます。
iframeの中身を読む際に必要だということで残していますが、
この記事のコードではiframeの中身は読みにいっていないです。

# content.script

全コードはこちらになります。

```javascript:全コード
/**
* 発火タイミング：ページを開いたとき
* ① 要素を読み込めたタイミングまで待機処理
* ② 「全てのセルを実行」クリックのトリガー設定
*/

$(window).on("load", () => {
  // メニューが読み込まれるまで待機処理
  const enableGetElem = () => {
    let target = $('#status-icon');
    if (target) {
      
      clearInterval(intervalID);
      setText();
      setRunallClicked();
    }
  }
  let intervalID = setInterval(enableGetElem, 3000);
});


/**
* 発火タイミング：ページの読み込み完了時
* メニューの右の箇所に「実行状況」というテキストを表示
*/

function setText() {
  const div = $('<div>')
    .addClass('goog-menu-button goog-inline-block')
    .attr({
      'id': 'execution-status',
      'aria-expanded': 'false',
      'aria-haspopup': 'true',
      'style': 'user-select: none;'
    });

  const indicator = $('<colab-last-saved-indicator>')
    .attr({
      'command': 'show-history',
      'style': 'user-select: none;',
      'aria-haspopup': 'true-status',
    }).text('実行状況');

  div.append(indicator);
  
  $('#top-menubar').append(div);
}


/**
* 発火タイミング：ページの読み込み完了時
* 「全てのセルを実行」のクリックイベントの設定
*/

function setRunallClicked() {
  $('div.goog-menuitem').on('click', () => {
    console.log('「全てのセルを実行」をクリック');
    $('#execution-status').text('実行中');
    checkRunning();
  });
}


/**
* 発火タイミング：「全てのセルを実行」クリック
* ① 実行中に発生する.runningが存在するかをチェック
* ② .runningがなくなったら、エラーが発生したかのチェックする関数を実行
*/

function checkRunning() {
  const elems = $('.cell.code.icon-scrolling');

  // runningのクラスがあるかの確認
  const hasrunning = () => {
    const elems = $('.cell.code.icon-scrolling');

    for (const elem of elems) {
      if(elem.classList.contains('running')) return;
    }
    clearInterval(intervalID);
    
    console.log('実行完了');
    checkError();
  }
  let intervalID = setInterval(hasrunning, 3000);
}


/**
* 発火タイミング：「全てのセルを実行」での実行完了
* case-1 エラーがない場合は「実行完了」というテキスト表示
* case-2 エラーがある場合は「エラー発生」とエラー箇所に飛ぶリンク作成
*/

function checkError() {
  const iframe = $('.outputview > iframe');

  if (iframe.length) {
    console.log('エラー発生');
    const url = location.href.match(/.+(?=#)|.+/)[0];
    let id = iframe.parents('.cell.code.code-has-output.icon-scrolling').attr('id');

    $('#execution-status').html(`<a href="${url}#${id}">エラー発生</a>`);

  } else {
    console.log('エラーなし');
    $('#execution-status').text('実行完了');
  }
}



```

作成中に少しハマった、疑問に思ったことを補足していきます。


# 特定の要素を読みこむまで待機処理

`manifest.json`で指定している`"run_at": "document_end"`でドキュメント読み込み後に
content.jsの読み込みにしていたり、
一応全コードが読み込むまでの待機処理に

```javascript:待機処理
$(window).on("load", function)
```

を入れているのですが、うまくいかなかったです。
なぜかメニューが表示される前に拡張機能が実行されてテキスト表示
されないことが多かったので、動的に作成される要素が作成されたのちに
実行させるという処理を`setInterval`メソッドで行っています。

```javascript:待機処理

$(window).on("load", () => {
  // メニューが読み込まれるまで待機処理
  const enableGetElem = () => {
    let target = $('#status-icon');
    if (target) {
      
      clearInterval(intervalID);
      setRunallClicked();
      setText();
    }
  }
  let intervalID = setInterval(enableGetElem, 3000);
});

```

本当は下記画像の部分のチェックがつくまでの待機処理をしたかったのですが、
その要素が取れず諦めました。

![2020-12-13_13h25_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/428239/5a129a91-6ca7-0409-5a21-3f1349377c64.png)


# エラー発生時のスクロール

Google Colaboratoryでは基本的に特定のセルに飛ばすために、
下記のURLで設定されています。

```
https://colab.research.google.com/drive/{colab_id}#scrollTo={cell_id}
```

セル1つ1つにIDが降られていて、そのIDを変更することによって移動させているのですが、
リンクを作った際はセルの中にいる状態でURLをクリックするとうまく遷移しますが、
セル外でURLをクリックすると対象セルまで飛びませんでした。

なので遷移先のURLは

```
https://colab.research.google.com/drive/{colab_id}#cell-{cell_id}
```

となるようにしています。

```javascript:該当箇所
function checkError() {
  const iframe = $('.outputview > iframe');

  if (iframe.length) {
    console.log('エラー発生');
    const url = location.href.match(/.+(?=#)|.+/)[0];
    let id = iframe.parents('.cell.code.code-has-output.icon-scrolling').attr('id');

    $('#execution-status').html(`<a href="${url}#${id}">エラー発生</a>`);

  } else {
    console.log('エラーなし');
    $('#execution-status').text('実行完了');
  }
}
```

# 終わりに

本当はエラー内容も取得して表示させる予定でしたが、
エラー内容はiframeで作成されていて、SOPの制約で
内容の取得ができませんでした。

どなたか実現可能な方法がございましたら、教えて頂けると嬉しいです。

見た目もよくないのでもう少しよくしていきたいと思いつつ、
あまり気力は湧いていないです。