ログ日記

作業ログと日記とメモ

Chrome の拡張機能で Uncaught TypeError: Cannot read properties of null (reading 'removeEventListener') のエラーが出る

Youtube の埋め込みでJavaScriptのエラーが出る。

Uncaught TypeError: Cannot read properties of null (reading 'removeEventListener')
    at YouTubeDelegate.self.unregister_element (<anonymous>:194:37)
    at remove_element (<anonymous>:406:43)
    at MutationObserver.<anonymous> (<anonymous>:428:21)
YouTubeDelegate.self.unregister_element @ 
remove_element @ 
(anonymous) @ 
childList (async)
mp @ www.youtube.com/s/player/0c96dfd3/www-embed-player.vflset/www-embed-player.js:1186
(anonymous) @ www.youtube.com/s/player/0c96dfd3/www-embed-player.vflset/www-embed-player.js:1343
(anonymous) @ www.youtube.com/s/player/0c96dfd3/www-embed-player.vflset/www-embed-player.js:575

Chrome のシークレットモードでは出ないので、何かの拡張が影響している模様。


stackoverflow.com

Ok, for future reference, it was a bug caused by "Disable HTML5 Autoplay" addon in chrome, I've created issue on their github.

なるほど。
Disable HTML5 Autoplay の拡張機能を停止したらエラーが無くなった。

今見ると、大きく

NOTICE
This extension is currently unmaintained.
The former developer will no longer be providing support.
If you would like to know the circumstances or develop this extension, consuit the sidebar.

と書かれていた。
もう必要ないのかな?

github.com
ここを読むと、まだまだ必要っぽい。

HTML5 Autoplay Blocker という拡張もあるけど、利用者数が一桁違うんだよなあ。
ひとまず放置で。

Google Closure Library を使ったJavaScriptをブラウザ無しでテストする

試行錯誤して長くかかったのでメモ。


まず、Closure Library は最初の頃はJSのファイルを羅列したhtmlを読み込んでテストする方式だったと思う。
JsUnit がその形だったかな?
CIとかが手軽に出来なかった頃はそれでも問題なかった。今ではテストのハードルがぐっと上がるので、ブラウザを自分で開いて確認するのはよろしくない。

jsunit test report not showing tests · Issue #869 · google/closure-library · GitHub

昔から使っていた人も他のツールに移っていっている模様。



今では(というか数年前?)ブラウザをコマンドで立ち上げてブラウザ機能を使いつつ、コンソールに結果を表示するという方法が取られるようだ。
blockly/run_mocha_tests_in_browser.js at master · google/blockly · GitHub
Googleのプロジェクトでサンプルを見れる。
このプロジェクトだとES5で書かれているから問題ないようだけど、ES6のmoduleを使うと、file:// で読み込めない問題がある。別途Webサーバーを立ち上げないといけない。
E2Eテストが遅いからJavaScriptだけでテストしたいのに、こんなに色々な準備が必要なら本末転倒である。見た目は綺麗で良い感じなんだけど。


かと言ってシンプルにmocha だけ使っても、documentが無いのでクライアントのテストができない。
Mocha - the fun, simple, flexible JavaScript test framework



MVC的にうまく切り分けられているならModelだけテストできそうだけれども、Closure LibraryはControl系コンポーネント以外はViewもゴチャ混ぜになっている。
そういうわけで色々試した結果、mocha と jsdom を使うことにした。
mochaはテストフレームワーク、jsdomはdomを提供してくれる。
(mocha-jsdomというパッケージもあるが、更新されていないようだった)

npm install --save-dev mocha
npm install --save-dev jsdom


mochaディレクトリを作り、初期化用ファイルを置く。

mkdir mocha
vi mocha/init.js
vi mocha/accordion_test.js


init.js

require('../lib/closure-library/closure/goog/bootstrap/nodejs.js');

// Polyfill for encoding which isn't present globally in jsdom
var util = require('util');
global.TextEncoder = util.TextEncoder;
global.TextDecoder = util.TextDecoder;

goog.nodeGlobalRequire('build/mocha_deps.js');

jsdomに無い関数を割り当てたりしながら、deps.jsも読み込む。
nodeを使いつつモジュール以外のファイルを読み込む場合は goog.nodeGlobalRequire を使うようだ。


mocha_deps.js は依存関係が書かれたファイル。
pathの扱いがややこしく、普段Closure Libraryで使っているものをそのまま使えなかった。base.jsとnodejs.jsはpathが一段深くなるので、deps.jsを生成するときのroot_with_prefixの指定を一段深くする必要があった。
これは別のファイルを生成することに。


mocha_js.sh

#!/bin/bash

set -eux

cd `dirname $0`/../

./lib/closure-library/closure/bin/build/depswriter.py \
    --root_with_prefix="js ../../../../js" \
    > ./build/mocha_deps.js


実際のテストのファイルはこんな感じ。


accordion_test.js

var JSDOM = require('jsdom').JSDOM;
var assert = require('assert');
var FakeTimers = require("@sinonjs/fake-timers");

goog.require('app.anim.Accordion');
var Accordion = goog.module.get('app.anim.Accordion');

goog.require('goog.style');
var clock;

describe('app.anim.Accordion', function() {
  before(function(){
    var dom = new JSDOM(
      '<html><body><form name="form">'
        + '<input type="checkbox" name="check">'
        + '<div id="area" style="display:none">test</div>'
        + '</form></body></html>');
    global.window = dom.window;
    global.document = window.document;

    this.check_ = document.forms.form.elements.check;
    this.area_ = document.getElementById('area');

    clock = FakeTimers.install();

    this.nowOrig_ = goog.now;
    goog.now = (function() {
      return +new Date();
    });
  });
  after(function(){
    this.check_ = null;
    this.area_ = null;

    clock.uninstall();
    goog.now = this.nowOrig_;
  });
  describe('#decorate()', function() {

    it('can decorate Accordion', function() {
      var target = new Accordion(this.check_, 100);
      target.decorate(this.area_);
      assert.equal(target.isInDocument(), true);
    });

    it('should change shown and running animation', function() {
      var target = new Accordion(this.check_, 100);
      target.decorate(this.area_);

      assert.equal(goog.style.isElementShown(this.area_), false, 'element is not shown before starred');
      this.check_.dispatchEvent(new window.Event('change'));

      assert(target.anim_, "exists anim object");
      assert.equal(target.anim_.isPlaying(), true, 'anim is started');
      assert.equal(goog.style.isElementShown(this.area_), true, "element is shown just after clicked");

      clock.tick(20); // start delay

      assert.equal(target.anim_.isPlaying(), true, 'anim is still started');
      assert.equal(goog.style.isElementShown(this.area_), true, "element is shown");

      clock.tick(80);

      assert.equal(target.anim_.isPlaying(), false, 'anim is stopped');
      assert.equal(goog.style.isElementShown(this.area_), true, "element is shown");
    });
  });
});

チェックボックスがオンされるとアコーディオンがアニメーションで開く、というコンポーネントのテスト。
ハマりポイントが結構あった。


最初は init.js でjsdomを読み込んでいたけれど、テストごとに別のhtmlを使うと思ってtest.jsに移動した。逆に言うと、htmlが同じなら*test.jsを同じファイルにまとめられる。


ES6を読み込む場合は goog.require と goog.module.get の両方が必要なことに注意する。


Closure Libraryの時刻系処理は全てgoog.nowを使っていて、それは読み込み時に既にnew Date()が割り当てられているので、goog.nowも置き換える必要がある。めっちゃライブラリのソース読んだ。
domの幅は取れなかった。画面が無いもんね。なのでstyleを確認。


イベントはjsdomで作ったwindowのEventをnewすればOK。




最後に、GitLab用のCIファイルを書く。
.gitlab-ci.yml

mocha_test:
  image: registry.gitlab.example.com/app/nodejs-python:12.0
  stage: test
  before_script:
    - npm install
  script:
    - ./bin/mocha_js.sh
    - ./node_modules/mocha/bin/mocha --file mocha/init.js "mocha/**/*test.js"
  except:
    - schedules

nodejs-python は自分でビルドしてGitLabのレジストリに登録した。


Dockerfile

FROM node:12-buster-slim

RUN apt-get -y update && apt-get -y upgrade && apt-get -y install python

バージョンが古いのは、今使っているdebianに合わせるため。普通にbuster-slimからapt install nodejs でも良かったかも。



キャッシュが無い状態で1分半、キャッシュがあれば53秒だった。
なかなか良い感じではないかな。


これでやっとJavaScriptのテストが書けるという最低限の状態にできた。

Debian の /bin と /usr/bin と debootstrap

https://n314.hatenablog.com/entry/2021/10/07/200305 の続き。



/bin/ping と /usr/bin/ping のバイナリが異なることに気付いた。

/bin/ping ならゲストからでもエラーが出ない。
でもPATHの設定で/usr/bin/ping が優先されるのでエラーが出るようだ。
/usr/bin/ping は getcap で /bin/ping = cap_net_raw+ep が付いていない。更新日も2018年で/bin/pingは2021年だった。


エラーが出ない方のゲストは /usr/bin/ping と /bin/ping が同一で、ホストは /usr/bin/ping が存在していなかった。


UsrMerge - Debian Wiki
debootstrap を実行したタイミングによって異なっている?


iputils-ping を削除すると /bin/ping は削除されるが /usr/bin/ping はそのままのようだ。
これと同じように、openssl系のライブラリもどこかに優先されるPATHで古いバージョンが残っている…?

2021-03-28 のiputils-ping の更新で、 /bin/ping, /usr/bin/ping の両方が更新されているパターンと /bin/ping だけ更新されているパターンがあるっぽい。


これは、もうこの状態になったらどうしようもないのかな。エラーが出るサーバーだけ手動で /etc/ca-certificates.conf を変更していくしかない?

2021-10-01 に Let's Encrypt の SSLでエラーが出る模様

systemd-nspawn ゲストの一般ユーザーでネットが繋がらない。
何もしてないのに壊れた。

$ git remote show origin
fatal: unable to access 'https://example.com/app/app.git/': server certificate verification failed. CAfile: none CRLfile: none
$ ping localhost
ping: socket: 許可されていない操作です

$ LANG=C ping localhost
ping: socket: Operation not permitted

検索するとSSL系の設定やsuidの記事が出てくるが、違う感じがする。pingは元々 /usr/ping は u+s ではない。
libcap2-bin 系も違う。別の環境ではgetcapの結果は同じなのに使えたりするので。

ログにも異常は見つけられず。
pingはrootだと問題なさそうなので、権限系っぽいが…。

ホストごと再起動してみるも直らず。
opensslは自動アップグレードがかかっているけど、それだとpingは関係ないよねえ…。
systemdのセキュリティ更新は二ヶ月以上前で、その後は問題なかった。



pingが問題ないマシンもあるので、違いを見てみる。
grep Cap /proc/$$/status の結果や getcap /bin/ping の結果は同じ。

$ grep Cap /proc/$$/status
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 00000000fdecbfff
CapAmb: 0000000000000000

# getcap /bin/ping
/bin/ping = cap_net_raw+ep

他にも試す。

export GIT_SSL_NO_VERIFY=1

あれ?これでいけた。
もしかして最初からpingは使えなかった?
うーん…。でもopensslの更新も二ヶ月前だった。なんで?




9/27は問題なし、今月になってから問題になったということは…

www.walbrix.co.jp

もしかしてこれ?
wgetはダメだった。curlはいける。
git は rootでもダメだった。

openssl が 1.1 なので完全にスルーしてた。ホストもゲストも同じバージョンなのにゲストだけエラーが出る。何かパッケージが足りないのだろうか?


/usr/share/ca-certificates/ にも違いが無い。
update-ca-certificates をやっても変わらない。

/etc/ssl/certs/ca-certificates.crt もエラーが無いゲストとエラーがあるゲストで同じだった。…うーん。






www.mail-archive.com
stackoverflow.com

最悪ローカルのcrtを何とかすればいいっぽいけど…。普通にパッケージ更新していれば大丈夫だとか、update-ca-certificatesでうまくいくって書いてあるよね。

openssl s_client -CApath /etc/ssl/certs -showcerts -connect lists.debian.org:443 < /dev/null

openssl のコマンドでは成功するけど、wgetが成功しない。分からん…。


community.letsencrypt.org

新しい環境でダメな人も居るみたい。


マジで困った。



/etc/ca-certificates.conf の

mozilla/DST_Root_CA_X3.crt

この行の先頭に ! を入れて、update-ca-certificates をすれば取りあえずはしのげる。
でも、この手作業の対応をそれぞれのサーバーでやる…?まったく同じバージョンで問題ないサーバーもあるのに…?
うーん…すっきりしない。

update-ca-certificates --fresh --verbose

でも変わらず、wgetでエラーが出るサーバーとエラーが出ないサーバーがある。

muttを設定する

ちょっとミーハーな心持ちで、以前挫折したCLIメーラーをインストールしてみる。
一応
GnuPG を使えるメーラーを探す - ログ日記
の続き。


大量のメールを処理できるNeomutt - Solist Work Blog
ここを読んでNeomuttが気になったけど
NeomuttでGmailにOAuth2.0する - Qiita
Debian(buster) の場合はMuttよりNeomuttの方が古いっぽいので。
bullseye なら大丈夫っぽい。


muttをインストールすると

/usr/share/doc/mutt/examples/mutt_oauth2.py

があるので、どこかのbinにコピーしてくる。

python のファイルの上の方に registrations 変数があって接続情報が書かれているので、直接編集。
ENCRYPTION_PIPE にもGPGで使っているメールアドレスを書く。


/usr/share/doc/mutt/examples/mutt_oauth2.py.README を読みつつ
Mutt - ArchWiki
ここも読みながら muttrc を設定する。


先にGoogleAPIでoauthのIDを発行しておく。テスト用でいけた。


とりあえず接続テストは

./bin/mutt_oauth2.py user@example.com.tokens --verbose --authorize

のようにして質問に答えていってから

./bin/mutt_oauth2.py user@example.com.tokens --verbose --test

で確認。


muttrcでのGmailの設定は

set from="user@example.com"
set realname="User Name"
set imap_user="user@example.com"
set folder="imaps://imap.gmail.com/"
set smtp_url="smtps://${imap_user}@smtp.gmail.com:587/"
set imap_authenticators="oauthbearer:xoauth2"
set imap_oauth_refresh_command="/home/user/bin/mutt_oauth2.py /home/user/.mutt/${imap_user}.tokens"
set smtp_authenticators=${imap_authenticators}
set smtp_oauth_refresh_command=${imap_oauth_refresh_command}

このように書いた。

あとは設定を適当なサンプルから持ってきた。

set spoolfile = "+INBOX"
set imap_check_subscribed
set hostname = gmail.com
set mail_check = 120
set timeout = 300
set imap_keepalive = 300
set postponed = "+[GMail]/Drafts"
set record = "+[GMail]/Sent Mail"
set trash = "+[GMail]/Trash"
set header_cache=~/.mutt/cache/headers
set message_cachedir=~/.mutt/cache/bodies
set certificate_file=~/.mutt/certificates
set signature =~/.mutt/signature


Gmailを見れるようにはなった。
でも、使えるかと言われると…うーん。
初めてvimを起動して終了方法すら分からなかったときような感じ。


Claws MailじゃなくてSylpheedを試してみようか。

DebianでVMwareが遅い問題は解決してなかった

https://n314.hatenablog.com/entry/2021/01/28/104312
こっちの設定をやってしばらく使ってるけど、やっぱりVMwareの起動直後はkcompactd0がフルにCPUを使って3分ぐらいほとんど操作できない。

/sys/kernel/mm/transparent_hugepage/defrag の設定は madvise にしていた。これってアプリケーションが必要だからデフラグを要求してるってことだよねえ…。
VMware用にメモリを12GB設定しているから、その分のデフラグは必要だってことなんだろうか。

固まるのは起動時だけで、最初にちょっと待てば後は大丈夫なんだけど。


qiita.com
今度VMware起動前と起動後で断片化をチェックしよう。

PHPStanとPsalmを混ぜた機能が欲しい

PHPStanはテンプレート+callableのネストに対応していた。

<?php

/**
 * @template A
 * @template B
 * @template X
 * @param callable(A,B):X $f
 * @return callable(A):(callable(B):X)
 */
function f2(callable $f)
{
    return function($a) use ($f){
        return function ($b) use ($f, $a){
            return $f($a, $b);
        };
    };
}

/**
 * @template A
 * @template B
 * @template C
 * @template X
 * @param callable(A,B,C):X $f
 * @return callable(A):(callable(B):(callable(C):X))
 */
function f3(callable $f)
{
    return function($a) use ($f){
        return function($b) use ($f, $a){
            return function($c) use ($f, $a, $b){
                return $f($a, $b, $c);
            };
        };
    };
}

function str_repeat_split(string $s, int $time, string $separator):string
{
    $arr = [];
    for ($i = 0; $i < $time; $i++){
        $arr[] = $s;
    }
    return implode($separator, $arr);
}
function test(): string
{
    $f = f3('str_repeat_split');
    $g = $f('abc');
    $h = $g(1);
    // $h(1);
    $h("\n");

    $i = f2('str_repeat');
    //$j = $i('abc')('def');
    $j = $i('abc')(2);

    $k = f2('str_repeat')('abc');
    //$l = $k('def');
    $l = $k(2);

    // $m = f2('str_repeat')('abc')('foo');
    $n = f2('str_repeat')('abc')(2);
    return nl2br($n);
}

コメントを外すと正しいエラーが報告される。

ここで、括弧がちょっとめんどいので

<?php

$n = f2('str_repeat')('abc')(2);
$n = f2('str_repeat', 'abc', 2);

引数と関数呼び出しの区別を無くして、並べて呼び出せるようにしたい。

機能だけなら
github.com
昔作ったんだけど、型チェックが効かない。


PsalmのConditional return typesの機能を使ってfunc_num_argsで戻り値の型を条件分岐させたい。
でもPsalmは

 * @return callable(A):(callable(B):(callable(C):X))

みたいな複雑な型に対応してないっぽい。



github.com
PHPStanにもconditional return typesの機能を付ける話は出ているみたい。

That's what this open feature request asks for, it's on my roadmap to implement this.

issue登録者が間違ってクローズしたものを作者が再オープンしていたので、しばらく待てば実装されるんだろうか。
もうすぐ1年経ってしまうけれども。