Translate

BTemplates.com

Powered by Blogger.

2019年8月31日土曜日

Chrome拡張のUnchecked runtime.lastError: Could not establish connection. Receiving end does not exist.というエラーを解決する


◆事の起こり

 Chrome拡張を作成している際に、ボタンをクリックしたときに
ブラウザの開いている全てのタブのURLが欲しい場面があった。

それ自体は、chrome.tabs.queryで取得できるのだが、
chrome.tabs.queryは、background.jsでないと動かないので
content_script.jsとbackground.jsで通信する必要があった。

しかし、content_script.jsでchrome.runtime.sendMessageを使って
background.jsに送信し、background.jsでchrome.runtime.onMessage.addListener
を使って受診し、sendResponseで送り返しているはずなのだが、
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.というエラーがログに表示された。


◆解決方法

私の場合だと、以下の三つを行うことで解決できた。

1.manifest.jsonに以下の内容が記述していなかった。
  "background": {
    "scripts": [
      "src/js/background.js"
    ]
  },

2.chrome.runtime.onMessage.addListener()のfunctionの中の一番最後に
return trueを記述していない

3.修正後も拡張機能のページ(chrome://extensions/)のエクステンションの更新ボタンをクリックして更新していなかった為である。




◆実際に試したこと

調べたところ、ネットに出ているのは試したのは以下の二つである。
・ほかの拡張機能が悪さをしているので、他の拡張機能を全部オフにしよう。
・エラー名の通り接続できていないので、接続できるまでsetTimeout()で呼び出せばいいんじゃないのか?

結論から言えば、今回のケースだと全部駄目だった。

なお、最初に記述した忙しい人の為の解決方法の内容を全て行った上で
仮にsetTimeoutのやり方で実装する場合は
以下のように実装すれば動くことは確認できた。

var getTabUrlsButton =
  '<button type="button" class="get_tab_urls_button">ブラウザのタブのURL取得</button>';
$ ('.getTabsUrlsButton').append (getTabUrlsButton);
$ ('.get_tab_urls_button').click (function (e) {
  ping ();
});
function ping () {
  chrome.runtime.sendMessage ({}, function (response) {
    if (chrome.runtime.lastError) {
      setTimeout (ping, 1000);
    } else {
      if (response) {
        $ ('.getTabUrlText').html (response.urls);
      }
    }
  });
}


*参考サイト

・特定のChrome拡張機能をOFFにするとエラーが消える事を検証している動画
Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.(YouTube)
・上と同じく特定のChrome拡張機能をOFFにするとエラーが消えるという話をしている
node.js - How to fix 'Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.' - Stack Overflow

・回答の一例でのsetTimeoutの話を参考にさせてもらった
Chrome Extension message passing: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist - Stack Overflow


◆その他

・調べてもpop.jsとbackground.jsの通信による結果のケースが多く
content_script.jsは特別なケースなのかと思っていたが、単にcontent_script.jsで書く人がいなかっただけのようだった。

・background.jsから、content_script.jsにデータを渡す際にタブのIDの指定が必要だという情報をネットで見かけたが、今回の件に限って言えば必要なかった。

・これとは直接は関係ないが、chrome.tabs.query()を使った際にsendResponse()を
chrome.tabs.query()のfunction外で宣言すると、chrome.tabs.query()が非同期なので
値を取得できずに初期値しか返さない。その為、きちんと動いているように見えるのに初期値しか返さなくて凄く混乱したことがあった。
これは、細かくconsole.logを使う事で明らかに呼び出しの順番がおかしい事に気が付くことで、なんとか判明することができた。


◆サンプル

content_script.jsとbackground.js間で通信をして
タブのURLを取得するChrome拡張を作りました。
gitを上げたので良ければ参考にしてください。
https://github.com/nononaga/myExample/tree/master/getTabUrls


2019-08-24 、26、27 、30 到達点メモ


正規表現で否定形を使いたい
(?!TEST)

上記のようにすると検索にTESTを含まないようにすることができる
なので、例えばTESは含むけどTESTは含まないとかしたい場合は

^(?!TEST)^(TES)

とすれば行けるはず

駄目だった
また今度参考サイトをちゃんと読みなおす

参考サイト:正規表現:文字列を「含まない」否定の表現まとめ | WWWクリエイターズ

条件を満たしたときにチェックボックスにチェックを入れる事に成功した。
どうやらnameとvalueを両方見なくても、条件が一致すれば操作できるようである
なぜかnameのほうで[]があり、上手く指定できなかった。
そのため、今回はvalueの方を指定して、チェックボックスにチェックを入れた

 $ ('[value="テストチェックボックス1"]').prop ('checked', true);

本来であるならば、恐らく重複を防ぐためにnameの方も指定しているのだろうが
今回でいえば、重複が起こる可能性は限りなく低いとみているのでこのままでいく。

どうも動的に作られたクリックできるボタンが上手くクリックイベントを起動させることができない。
どっかでそういう問題を見た記憶があるが、追加されたタグを削除することで対処ができたので良しとする。
今回は.remove()で対処した。同じタグをすべて消すことができる

$('.testDeleteTag').remove();

参考サイト:jQueryでDOM要素を削除する:remove(), empty(), unwrap(), detach() | UX MILK

配列の全部検索はforEachでもできるが、下のサイトによるとどうやら非推奨らしい。
コードを見た時にこれは一体何なのか?ということをわかりやすくするためらしい。

参考サイト:JavaScript で forEach を使うのは最終手段 - Qiita

実際、別にforEachに拘っているわけではないので
mapで書いてみたところ、書けたのでそれで良しとする


var categories = [
'test1',
'test_nononaga',
'testetstetst',
'Hogehoge',
'ホげほげ',
'その他',
];
categories.map (category => {
var tempValue = '[value="' + category + '"]';
$ (tempValue).prop ('checked', false);
});

参考サイト:Array.prototype.map()  by Mozilla Contributors is licensed under CC-BY-SA 2.5.

例えば、以下のタグがあるとして
hrefのデータを取得したいとする。
<a class="test_a_tag" href="https://www.nononagainfo.com/">
nononagaInfo
</a>

この場合は以下のように書けばよい
$ ('.test_a_tag').attr ('href');

参考サイト:jQueryのhref属性を取得する - コードログ

ajaxで色々やる際に参考にしたサイト
参考サイト:はじめてajaxを使うときに知りたかったこと - Qiita

新しくタブを開きたい場合は、以下のようにやればよい
window.open (urlData);

参考サイト:window.open  by Mozilla Contributors is licensed under CC-BY-SA 2.5.

たかだか、全部のタブを調べるという作業で何でこんなに迷走しているのか・・・。
問題1.background.jsでないと、 chrome.tabs.queryを使用することができない

問題2.contentsとbackgroundでデータのやり取りをするのだが
後述する問題3のこともあり、今一やり方がよくわかっていない・・・。

◆参考サイト
try catch and ...release: Chrome拡張機能のbackground.jsとpopup.jsで通信

問題3.google翻訳によるものなのでどこまで正確なのかはわからないが
どうやらチャンネルを開く前にrunetime.connect() を呼び出すとエラーになるらしい。
というか特定のバージョンからそのようなので、古い奴のコードだと普通に動かない

◆参考サイト
How to Fix: Could not establish connection. Receiving end does not exist - Bennett Notes


◆その他参考に成りそうなサイト
Chromeの拡張機能を作ってみる Vol.3 実装
僕が考えたChrome拡張機能を作るときのデザインパターン


content_script.jsでchrome.runtime.sendMessage()を使い
background.jsでchrome.runtime.onMessage.addListener()を書いているのに
ずっーーーーと、Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.というエラーがなぜ起こるのかがよくわからなかった。

調べたところ他の拡張機能が干渉して起こるケースもあるらしいのだが
今回に限って言えば、これ以外全部動かないようにしていたので
これが原因では無かった。

色々調べた結果、どうやらマニフェストファイルに、
"background": {
    "scripts": ["src/js/background.js"]
  },
という記述が無かったのが原因のようだった。

また、自分が変更の処理を行った場合、
画面更新で対処していたのだが、background.jsやマニフェストファイルは
其れでは対応できない。

なので、下の画像のように拡張機能のページ(chrome://extensions/)のエクステンションの更新ボタンをクリックしてやるのが一番確実。









◆参考サイト
Message Passing - Google Chrome

かえせよっ!おれのじかんっ!かえせよっっ!!!


他に躓いたところとして、二つあって
一つはtabの一覧のURLを取得する方法はかなり苦労した。

chrome.windows.getAllの方法で実装する方法を発見した。
例えば、getAllの時にpopulate:trueを設定しないと
必要なものが取得できなかったり、
返り値がwindowsという複数形だという事に気が付かなくて
大分悩んだりとか、本当に色々大変だった。

◆参考サイト
javascript  ー  Chromeで開いているタブを取得する - コードログ
chrome.windows - Google Chrome


ただ、全てが終わってこれを書いているときに検証してわかったのだが
chrome.tabs.queryでやったほうが簡潔だし
わかりやすかった・・・。

実装例
  chrome.tabs.query ({}, function (tabs) {
    var urls = '';
    tabs.map (tab => {
      console.log (tab.url);
    });
  });


かえせよっ(以下ry

前にやchrome.tabs.queryで出来ていたと思っていたのですが
できなかったのでchrome.windows.getAllの方法でやっていました・・・。
出来なかったのは、マニフェストファイルのpermissionsの中にtabsが
無かったのが原因だったのかもしれません。

もう一つは、何度通信ができても値が初期値しか返ってこないことがあった。
具体的には以下のように書いていた
  var urls = '';
  chrome.tabs.query ({}, function (tabs) {
    tabs.map (tab => {
      urls += tab.url + '</br>';
    });
  });
  sendResponse ({
    urls: urls,
  });

どうも、console.logで調べた感じだとchrome.tabs.queryしかり、chrome.windows.getAllは非同期で動いているらしかった。
おそらく、値が取得できていない状態でsendResponseが動いたので
空データしか受け取れない状態になっていたと思われる

それを踏まえた上で、今回は以下のように修正した。
具体的には、chrome.tabs.queryの中に、sendResponseを含めることにした。
  chrome.tabs.query ({}, function (tabs) {
    var urls = '';
    tabs.map (tab => {
      urls += tab.url + '</br>';
    });
    sendResponse ({
      urls: urls,
    });
  });

今回はこれで解決できたが、複数動かすものとか考えるとこれだと
明らかに問題が起こる。
thenについてちゃんと調べておかないと・・・。

◆参考になりそうなサイト
Promiseを使う  by Mozilla Contributors is licensed under CC-BY-SA 2.5.

今回の開いているタブのURLを取得するChrome拡張をつくったので
それをGitに上げときましたので参考にしたい方はどうぞ
https://github.com/nononaga/myExample/tree/master/getTabUrls

単独記事を作ろうと思ったが、力尽きたので今日はここまで。


2019年8月21日水曜日

2019-08-09 08-17 08-20 08-21 到達点メモ


今回は、沢山ある<li>の中から<h3>のデータを取得し
その中でNGワードがあるものを非表示にするやり方

$ ('li').each (function (index, val) {
var tempDOM = $ (val);
var tempRegExp = new RegExp ('NGワード|禁止ワード');
var searchText = tempDOM.find ('h3').text ();
if (searchText && searchText.match (tempRegExp)) {
var searchID = tempDOM.attr ('search_id');
var hiddenElem = $ ('[search_id="' + searchID + '"]');
hiddenElem.attr ('hidden', true);
}
});

まず、$ ('li').eachで<li>のデータを取得できるようにし
これは、そのままだとhtmlデータが入ったテキストのままようなので
これを以下のようにセレクタにしてあげます。
var tempDOM = $ (val);

◆参考サイト
jQueryのeach()メソッドの基本的な使い方
jQueryのセレクタ解説 - SMART 開発者のためのウェブマガジン


matchで使うようにRegExpを作ります。そのまま書いてもいいのですが
今後、NGワードが増えていくのでこちらの方が見やすいと思ったので
こういう形にしました。
var tempRegExp = new RegExp ('NGワード|禁止ワード');

◆参考サイト
【JavaScript】~を含むかのチェック - Qiita


次にtempDOMのh3タグを見つけたいので、findで検索します。

今回は、構造上非表示にしたい<li>タグにsearchIDという属性があったので
これで対処しましたが、場合によっては他の方法を考える必要があります。
なお、searchIDのデータを取得したい場合は、以下のように指定します。
var searchID = tempDOM.attr ('search_id');

◆参考サイト
jQuery 属性値を取得/設定するサンプル(attr) | ITSakura
.attr() | jQuery API Documentation

属性の値で検索したいので以下のように設定します。
var hiddenElem = $ ('[search_id="' + searchID + '"]');

◆参考サイト
【jQuery】data属性に特定の値を持つ要素を指定する方法。


対象のliを非表示にしたいので以下のように設定します。
hiddenElem.attr ('hidden', true);

◆参考サイト
HTMLタグにhiddenを付ければ内容を非表示にできる | iwb.jp

*jQueryで、フォームに入力された値を取得したい場合

//間違い
$ ('.hogehoge_get_form_text').text ();

// 正解
$ ('.hogehoge_get_form_text').val ();

◆参考サイト
jQuery: フォーム要素からの入力値を取得するには?(val) - Build Insider


*jQueryで、labelの属性forを指定したい場合

$ ('label[for="hogehoge_point_label_for"]')

◆参考サイト
javascript-jQueryラベルの'for'属性セレクタ - コードログ

Ajax.getでURLからデータを取得しようとしたところ、コンソールログで以下の様なエラーが出た
jquery.min.js:2 Cross-Origin Read Blocking (CORB) blocked cross-origin response https://HogeHoge.JP with MIME type text/html. See https://www.chromestatus.com/feature/5629709824032768 for more details.

今一状況は飲み込めていないのだが、意図しないアクセスを防ぐために
クロスドメインという仕様があるらしい。
ただ、MDNの方を読む限りでは、オリジン間リソース共有というのが正しい単語のように思える。うーん・・・。

◆参考サイト
オリジン間リソース共有 (CORS)  by Mozilla Contributors is licensed under CC-BY-SA 2.5.
JavaScript のクロス ドメイン (Cross-Domain) 問題の回避と諸注意 - tsmatz
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. が出る。 - ミノタケ日記。
Cross-Origin Resource Sharing(CORS)を使用したHTTPリクエスト | 69log
An In-depth Look at CORS ー SitePoint

◆リクエストを調べる際に参考にしたサイト
cURLコマンドでレスポンスヘッダのみを取得する - Qiita

色々模索してみましたが正直よくわからねぇ・・・。
ただ、今回のケースでいえば、Chromeの仕様の影響が大きい様だったので
起動オプションで無効化のコマンドを使った方が解決として早かったです。

◆参考サイト
クロスドメイン制約を回避するChromeショートカットを作る - Qiita


チェックボックスにチェックを入れたい場合は、以下のようにする
$ ('input[type=checkbox][name=test_is_checkbox]').prop (
     'checked',
      true
 );
チェックを外したい場合は、trueをfalseに変えればよい。

ラジオボタンでチェックを入れる場合は、propでやったほうが良い。
何故かというと、以下のようにvalueの値が数字でない場合があるから。

$ ('[name=test_user_status][value=nononaga]').prop ('checked', true);

◆参考サイト
【jQuery】フォーム部品の取得・設定まとめ - Qiita

正規表現で特定文字列から一番最後まで削除したい場合は以下のように書く
今回はURLのパラメータやプロパティを削除したい時に利用した

url = url.replace (/(#.*$)|(\?.*$)/, '');

◆参考サイト
<a>: アンカー要素  by Mozilla Contributors is licensed under CC-BY-SA 2.5.
正規表現:「行頭」「行末」の表現と、応用例 | WWWクリエイターズ


ボタンクリックを使いたいと思っていたが、思った以上に実装が簡単だった。
$('#buttonID').click();

◆参考サイト
【jQuery】イベント発火方法 - Qiita

お蔭で今やっている作業がかなり楽になった上に早くなった。
今までだと登録する内容が良くて頑張れば一時間に20件登録が間に合うかどうかなのに
今だと少し気を抜いてやって50分くらいで20件登録できるようになった。
プログラミングで感動したのはかなり久しぶりかもしれない・・・。

今日はここまで

2019年8月7日水曜日

2019-07-29 07-31 08-05 08-06 到達点メモ


jQuery

.html()で出力をさせていたのだが
その際にコード上だと<br />なのに
何故か.html()で出力させた場合は、<br>になっていた。
調べてみると、そもそも<br />はXHTMLで使うものであって
HTMLは<br>になるらしい。

これが混在しているという事は、違いを理解して使っていないという事だよなぁ・・・。

参考サイト:<br> と <br /> タグの違い 【HTML と XHTML 】
https://ameblo.jp/midorieng03/entry-10929028852.html

メソッド名を踏まえて考えると <br /> が自動的に<br>になるのは
まぁ納得かな。

class内でclickの中にclassのメソッドを呼び出すのは無理っぽい?
*方法はあるかもしれんが、分からんかった。
一先ず、下記のようにclickと同じネストで関数を定義すれば
呼び出すことは出来る。

class RewriteTestPage {
  constructor () {}
  addTestButton () {
    function testCall () {
      console.log ('test');
    }

    var h1Html = '<button type="button" class="test_title">テスト</button>';
    $ ('div.test_add td').append (h1Html);
    $ ('.test_title').click (function (e) {
      testCall ();
    });
  }
}

例えば、長くなった且つごちゃごちゃになったjsを分割したい時などに
jsからjsを呼び出したいとする
jsでjsを呼び出すには基本的には、importで大丈夫なのだが、
エクステンションの場合はちょっと話が異なるようである

大まかに項目を上げると
・呼び出し元のクラスは、static newとexport文を宣言する必要がある
・content_main.jsとcontent_script.jsを作成し
content_scriptでcontent_mainを呼び出すようにする
・content_mainで呼び出したいクラスのファイル名を書く

参考サイト:chrome-extension-es6-import
https://github.com/otiai10/chrome-extension-es6-import/tree/81199dcf1caa25df566e5e1dd9837989f151b775


あと、良く理由が分かっていないのだが
先ほど記載した参考サイトを元に行ってみたのだが
非同期処理内でwindow.onloadのfunction内で
クラス関数を実行しようとしても何故か実行しない。
window.onloadの部分をコメントアウトすると動くことは確認できている。


ちょっと書かないと特に縁のない言語はやはりすぐに忘れてしまう・・・。
変数を含んだ置換処理を行いたい場合は、
new RegExp()を使えばよい

*使用例
var tempHtmlString = $ ('.test_div').html ();
var tempRegExp = new RegExp (
'<div>' +
  testValue +
  '<\/div>(\n(.*?)|(.*?))<\/th>\n(.*?)<td'
);
tempHtmlString = tempHtmlString.replace (tempRegExp, 'TestReplace');

◆参考サイト
javascript:replace()で正規表現に変数を使う場合
RegExp  by Mozilla Contributors is licensed under CC-BY-SA 2.5.

他にも作業しようと思ったが、力尽きたので今日はここまで