たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
(原著:David Teller, 2020年8月20日、CC BY-NC 4.0で公開されている内容の全訳。Qiitaにもクロスポストしています。)
要約:Firefoxはかつて、XULとXPCOMに基づく偉大な拡張機能の仕組みを持っていました。この仕組みは長い間私達によく尽くしてくれました。しかし、Firefox開発者と拡張機能開発者の両方にとって、メンテナンスコストは増大し続けるばかりでした。ある面では、増大していくコストは、Firefoxをセキュアにしたり、高速化したり、新しい事を試したりするための努力を、少しずつ破壊していきました。また別の面では、増大していくコストはアドオン開発者のコミュニティを少しずつ破壊していきました。最終的に、古いアドオンの仕組みを守ろうとして数年を過ごした後、Mozillaは、この拡張機能の仕組みを廃止し、それより拡張性は劣るもののメンテナンスしやすいWebExtensions APIに置き換えるという、難しい決断をしました。この選択のおかげで、Firefox開発者は再び、セキュリティや安全性やスピードを改善するために必要な変更を行えるようになったのです。
ここ数日、私はFirefoxのユーザーと会話して、2020年8月のMozillaによるレイオフの結果に関する噂と事実を区別しようとしていました。その過程で何度か持ち出されたのが、Firefox Quantumへの移行におけるXULベースのアドオンの廃止のことでした。私は非常に驚きました。もう何年も前に起こったことについて、この選択に感情を害されたと感じている人が、コミュニティにまだいるということにです。
そして、誰かがredditで指摘していたとおり、私は、何故XULベースのアドオンの廃止以外に選択の余地が無かったかについて、私達が深いところを説明する機会をまだ持っていなかったということに気付きました。
そこで、アドオンとGeckoの内部事情の話に飛び込む準備ができている人向けに、これを機にもう少し詳しい話をしてみようと思います。
このところ狂ったような長文を立て続けに書いていて、いいかげん出涸らし感が出てきた気がするけど、このツイートからたらたら書き連ねたことの増補改訂版として残しておく。
Gitのデフォルトブランチ名「master」が奴隷制を想起させるさせないの議論を発端に、差別される側にとって「言葉狩り」に一体どういう意義があると考えられるのかを改めて考えたんだけど、釈然としないモヤモヤ、露悪的な言い方をしてしまうと「差別主義者って言われるのが怖くてビビって過剰反応してるだけなんじゃないの?」「現実にある差別構造の撤廃に切り込む方が大事だろうに、言葉遊びをしてるだけの人がなんで『これぞ先進的な人権感覚、皆も追従せよ』みたいなツラしてるわけ?」みたいな違和感はずっと残ったままだった。
なぜそんなにモヤモヤしてしまうのかというと、これって普段から日本でも見慣れてる、同調圧力に基づく自主規制の光景と変わらないと思うからだ。ナイフでの殺傷事件が起これば文脈を問わずナイフ描写がマスメディアから一斉に「自粛」で姿を消す。問題がありそうかなさそうかを個別に熟考する暇も無く。それとよく似てると思った。
1つ前のエントリにちょいちょい追記してるんだけど、見通しが悪くなってしまったので別エントリにした。
blacklistをblocklistにするとか、master/slaveを別の語に言い換えるとかの変更は、一体誰のためのもので、どういう意義があるのか? という問いに対して、1つ前のエントリを書き始めた時点でまだ自分は完全に腑に落ちる理解ができていなかったように思う。
その後、観測した反応や他の人による同じ件への言及を見ていて少しずつ、表題の話が腑に落ちるようになった。と同時に、改めて、先の言い換えを推進する流れの妥当性の怪しさを感じた。
自分がどう理解しどう腑に落ちたのか、今どのような疑問を持っているのか、を記録のためにまとめる。
Gitのデフォルトブランチ名は慣習的にmaster
とされてるんだけど、これがmaster/slave(主人と奴隷)つまり奴隷制に由来する表現であるとして、2020年5月25日にミネアポリスで起きた白人警官による黒人被疑者の殺人事件を契機に盛り上がりを見せているBlack Lives Matter運動の流れを承けて、別のブランチ名に変更しようという動きがある、という事を知った。実際に、GitHubの公式のコマンドラインツールで既定のブランチ名がtrunk
に変更された(……と例に挙げたけど、たまたまタイミングが一致しただけで、このプロジェクトでの変更は事件の前だったみたい)ほか、いくつかのプロジェクトも追従しているという。
結論から先に言えば、僕もこの判断に追従した。GitHubの僕のアカウント配下のリポジトリは現時点で186あって(hub api users/piroor | jq .public_repos
で調べたらそう言われた。プルリクエスト用の一時的なforkが結構あるのでそれで多くなってる部分はあると思う)、ひとつひとつ手でやっていると埒が開かないので、それぞれローカルでは1つの作業ディレクトリにcloneされているのをいいことに、WSL1のUbuntuのbashで、GitHubのリポジトリを操作するAPIを叩けるhubを併用して以下のようなワンライナーで一気にやることにした。
export NAMESPACE=piroor;
ls | while read path;
do
[ -d "$path" -a -d "$path/.git" ] &&
echo "checking $path";
(cd "$path";
export ORIGIN_INFO="$(git remote show origin)";
(echo "$ORIGIN_INFO" | egrep "Fetch URL: git@github.com:/?$NAMESPACE/[^\\.]+\.git") &&
(export REPOSITORY="$(echo "$ORIGIN_INFO" | grep 'Fetch URL' | egrep -o "([^/\.']+)\.git" | cut -d . -f 1)";
echo "updating $NAMESPACE/$REPOSITORY";
((git branch | grep '* master' &&
(git branch --move master trunk;
git push --set-upstream origin trunk));
(hub api "repos/$NAMESPACE/$REPOSITORY" | jq .default_branch | grep master &&
hub api "repos/$NAMESPACE/$REPOSITORY" -X PATCH -F default_branch=trunk);
(URL_BASE="https://travis-ci.org/$NAMESPACE/$REPOSITORY.svg\\?branch=";
git grep -E "${URL_BASE}master" |
cut -d : -f 1 |
uniq |
xargs sed -i -r -e "s;${URL_BASE}master;${URL_BASE}trunk;g" &&
git commit -m 'Migrate master to trunk' $(git grep -E "${URL_BASE}trunk" | cut -d : -f 1 | uniq) &&
git push))));
done
まずgit remote show origin
でリモートリポジトリとの対応付けを調べる。移行対象のリポジトリであれば、git branch --move master trunk
でブランチ名を変更して、GitHub上のデフォルトブランチもtrunk
に切り替えて、Travis CIのビルドステータス画像のURLに含まれているブランチ名もついでに更新する、という感じ。他にもまだ変えないといけない所は残ってると思うけど、それは気付いた時に追々直していこうと思ってる。
同様に各リポジトリをclone済みの他の環境では、ローカルリポジトリの情報を変更するだけなので、以下のようになるか。
export NAMESPACE=piroor;
ls | while read path;
do
[ -d "$path" -a -d "$path/.git" ] &&
echo "checking $path";
(cd "$path";
export ORIGIN_INFO="$(git remote show origin)";
(echo "$ORIGIN_INFO" | egrep "Fetch URL: (https://github.com/|git@github.com:/?)$NAMESPACE/[^\\.]+\.git") &&
(export REPOSITORY="$(echo "$ORIGIN_INFO" | grep 'Fetch URL' | egrep -o "([^/\.']+)\.git" | cut -d . -f 1)";
echo "updating $NAMESPACE/$REPOSITORY";
(git branch | grep '* master' &&
(git branch --move master trunk;
git branch --set-upstream-to=origin/trunk))));
done
ローカルにcloneされてないリポジトリは、今の所まだ手つかず。hubでどうにかできるだろうとは思ってるので、やったらまた追記する。
(6月15日追記)ローカルにcloneされてないリポジトリも全部やるワンライナーは以下のような感じになった(while
ループが二重になってまでワンライナーて……)。
export NAMESPACE=piroor;
export TOTAL_REPOS=$(hub api users/piroor | jq -r .public_repos);
export PER_PAGE=100;
seq 1 $((($TOTAL_REPOS / $PER_PAGE) + 1)) | while read page;
do
hub api "users/$NAMESPACE/repos?page=$page&per_page=$PER_PAGE" |
jq -r .[].name |
while read name;
do
[ "$(hub api "repos/$NAMESPACE/$name" | jq -r .default_branch)" = "master" ] &&
echo "updating $NAMESPACE/$name" &&
(export workdir="$(mktemp -d)" &&
echo " workdir: $workdir" &&
(cd "$workdir" &&
git clone "git@github.com:$NAMESPACE/$name" --branch master --single-branch &&
cd "$name" &&
git branch --move master trunk &&
git push --set-upstream origin trunk &&
hub api "repos/$NAMESPACE/$name" -X PATCH -F default_branch=trunk);
rm -rf "$workdir");
done;
done
API叩いて直接リポジトリをリネームするやり方が分からなかった(そういうのはない?)ので、一時的にcloneして作業するという形で解決してみた。プルリク用にforkした物までゴソッとやっちゃうので、気になる人は除外処理を入れて使おう。
自分はこのように移行したけど、既存ツールチェインが壊れる危険とかいろいろあるし、これが本来意図された通りの効果がある変更だという確証もないので、みんながみんなやるべきとまでは思ってない、という事も書き添えておく。
以下、技術的な話から離れて、このような言い換えの必要性と妥当性について今回色々調べた事・考えた事を、記録のために書き残しておく。
ネットは実名であるべきなのか匿名であるべきか、という話は何年かに一回くらい目にする議論な気がする。
自分は、「Piro」というハンドル(ペンネーム)で、匿名でWebを使い始めて、「OSSのライセンス文には本名を書かないといけないらしい」という誤解があって途中から実名も公表するようになったので、実名のメリットもデメリットも、匿名のメリットもデメリットも、全部一通り我が事として体験してきたと思う。
その上で思うのは、「匿名の連中は無責任だから全員実名を強制するべきだ」「いや匿名の方がいい、実名を使いたがるやつは売名目的だから出ていけ」みたいな、「自分がどうするか」ではなくて「みんなどうするべきか」でどっちかに寄せたがる発言をする人や、そういう言説は、あんまり信用しちゃいけないな、ということ。
まずかつての自分のように、こういうそれぞれのデメリットを分かっていなかった(言葉で知っていても実感はしていなかった)時点で、わかりやすいメリットだけ並べて「だからみんなそうするべきだ」って言ってる人は、視野が狭く考えの浅いアホなので、信用するに値しない。
また、匿名のデメリットも実名のデメリットも分かった上で、デメリットを伏せてメリットだけ並べて「だからみんなそうするべきだ」と言ってる人は、自分の持つ武器(社会的地位なり、相手を言いくるめる術なり)が最大の効力を発揮し、自分の持つ不利(社会的地位の無さなり、理屈のガバガバさなり)を最大限ごまかすのに、たまたまそっちの方が都合がいいからそう言ってるだけの事が多い(そういう発言を「ポジショントーク」と呼ぶ)。自分の都合のいいように世論を動かして自分が利益を得たいだけで、煽られた他人が実際に被る不利益の事なんか知ったこっちゃないのに、それを隠してる不誠実な人なので、これも信用してはいけない。
善意のアホが悪意のポジショントーカーに乗せられてる事もあるし、アホが無自覚にポジショントーカーになってしまってる事もある。結局ポジショントークをする人にロクな人はいない。「ポジショントークをしない事」イコール「信用に足る」という事では必ずしもない(信用する十分条件ではない)けれども、ポジショントークをしてる人の事は真っ先に「あ、この人は信用しちゃ駄目だ、信用しない人リストに突っ込んじゃっていいや」とバッサリ切ってしまっても、大抵は不都合がない(信用する必要条件ではある)。今の自分の感覚をなるべく正確に言い表すと、こんな感じになると思う。
ES moduleのコードでは「関数名や変数名の誤記」をESLintなどで容易に静的に検出できます。なので、JSで書ける物は片っ端からなんでもES moduleにしたくなるのですが、いわゆる自動テストのコードでは、この性質がかえって邪魔になることがあります。
たとえばMochaのテストケースには、何の前触れもなくdescribe()
やit()
などの関数が登場します。これらが「未定義の関数呼び出し」としてエラーにならないようにするには、テストと実装でESLintのルールを切り替えて警告条件を緩和したり、テスティングフレームワークの関数やオブジェクトを警告の例外に明示したりといった対策が必要になります。
しかし、警告を甘くすればテストだけ静的な検証が甘くなりますし、警告の例外を指定するのもテストの作成やメンテナンスが煩雑になります。テストの書きやすさ・維持しやすさと静的な検証の完全性とを両立しにくいのは、JSで物を作る時に個人的にずっと気になっていた点でした。
というわけで、このような難点がないPure ES modulesなテスティングフレームワークを作ってみました。
Node.js v13以上の環境で、npm install tiny-esm-test-runner
でインストールできます。
元々は、特定プロジェクトでのCI用に簡易的なテストランナーを書いて使い捨てにしていたのですが、それを複数プロジェクトで使い回すうちに、さすがメンテナンスが面倒だと感じるようになってきたため、この度一念発起してきちんと整備したという次第です。
テストはES moduleのファイルとして記述します。以下に例を示します。
ほんとに「女が強い時代」って、こういうこと? 「女が支配する社会」を描いたディストピア作品3選 - wezzy|ウェジー という記事が話題になっているのを観測した。自分の観測範囲では批判的な感想が多かったように感じるけれど、それは「この記事を批判的に見た人が、記事を紹介し、同様の批判的意見を紹介している様子」を観測したせいかもしれない。
男女の性役割逆転というのは昔から度々描かれているようで、家畜人ヤプーはその代表的な一作と言えるようだ(僕は江川達也氏による漫画版だけ見た)。僕は先の記事に挙げられている3作品はいずれも未見だけれども、近年の観測範囲でも、立て続けに2作品ほど男女の性役割逆転を描いた作品を見かけた。ただ、先の記事の3作品や「家畜人ヤプー」と、僕の観測した2作品とでは、描き方にどうも差があるように感じられた。
僕の観測した1作目は「貞操逆転世界」(原作:天原、漫画:万太郎)で、主人公が「性」に関する事だけ男女の性役割の逆転した世界に迷い込んでしまうという内容。先の記事で紹介されている「軽い男じゃないのよ」とプロットは似ていて、「街中に無意味に男の水着の広告が溢れている」のような描写も共通しているようだけど、設定としては以下の点が大きく異なる。
これらの事から、本作は「今まで優位な立場だった人が、劣位の立場に戸惑う」という内容ではなく、「今まで消費される性の立場だった人が、消費する性の立場に戸惑う」という内容になっている。
敢えて「優位・劣位」と書かなかったのは、本作では「高校生」という、性差が経済的な差や権力差にあまり結び付いていない年代の視点であるために、必ずしも「性役割が逆転したら女性が優位になっている」とは限らないからだ。
IQが20違うと会話が成立しない、みたいな話は昔からよく聞くけど、そもそも「自分自身が一般よりIQが高い天才です、一般人と会話が通じなくて困ってます」って当事者になってることがあんまり無いと思うので、実際そうなのか?ってのはよく分からない人の方が多いんじゃないかと思うんですよね。僕もそのクチで。
ツイッターみてたら、そういう場面において、一般よりもIQが高く「ギフテッド」と呼ばれる人の側が当事者として実際どう感じているのか、をご自身の体験から詳しく説明されている記事が流れてきて、読んでみたら、前々から「きっとこういうことなんだろうなあ」と考えてた事とわりと合致する話が書かれてたんですよ。
というのも、これって「自分がアニオタで、アニメにまったく興味がない人に話が通じない」みたいな場面にすごく似てるなって思ったんですよね。
こういうの、すごく身に覚えがあるんですよね。「オタク知識」の話でなくても、社会人だったら自分の業務分野・専門分野の知識とか、自分は滅茶苦茶詳しくてお客さんは全然詳しくないので説明に苦労するってのはすごくありふれた話だと思うんです。自分も今まさに、テクニカルサポートの業務でお客さんに説明する場面でそういう事はよくあるし、Linuxのシェルコマンドの解説漫画を描く上で大変な事もまさにそういう事だし。
で、「自分側が知識がありすぎて、知識が無い側の人に話が通じなかった」体験をそういう風に想起できる一方で、「自分側の知識レベルや頭の回転速度が低過ぎて、有能な相手がする話の内容がまるで理解できなかった」体験もまた、自分には想起できるんですよね。学校で先生の言う話が分からなくて授業についていけないとか、仕事の上で先輩の話しに全然ついていけないとか。むしろこっちの方が多いくらい。
なので、自分としては「IQが20も違うと会話が成立しない」という話の両方の当事者の感覚を想像しながら、先の記事を読んだんです。
それで思ったのが、「この記事を書かれた方(ギフテッドの人)には、もしかして、自分の話を相手に理解してもらえなかった経験はあっても、相手の話が自分には理解できなかった経験が無かったりするんだろうか?」という事で。
そもそも記事自体が「IQが高い側の弁明」という体裁だから敢えてそう書いたのかもしれないんですが、「やばい、相手の言ってることがまるで自分には理解できない。どんなに説明されてもチンプンカンプンだ」という感覚が、あまりその記事からは感じられないような気がして。
FirefoxのアドオンやChromeの拡張機能向けに、名前空間をまたいでDOMに変更を差分適用したい場面で使える、Virtual DOMでないReal DOMで差分適用する、webextensions-lib-dom-updater
という名前のライブラリをつくりました。
クライアント側でタブの情報を取得して、サーバー側でそれをレンダリングする、という場面であれば以下のようになります。
クライアント側(制御担当):
// IDからタブのオブジェクトを得る(WebExtensionsのAPI)
const tab = await browser.tabs.get(tabId);
// プロセスをまたいで、レンダリングして欲しい内容を送る
browser.runtime.sendMessage(
'受信側の識別子',
// ↓テンプレート記法でHTMLのコード片をそのまま生成
`
<span id="tab"
class="${tab.active ? 'active' : ''}">
<span id="throbber"
class="${tab.status}">
<span id="throbber-image"
class="${tab.status}"></span>
</span>
<img id="favicon"
class="${tab.status}"
src="${tab.favIconUrl}">
<span id="label">${tab.title}</span>
</span>
`.trim()
);
サーバー側(画面描画担当):
import { DOMUpdater } = './dom-updater.js';
// 他のプロセスからのメッセージを待ち受ける(WebExtensionsのAPI)
browser.runtime.onMessageExternal(message => {
// 反映先の要素
const before = document.getElementById('container');
// 反映する内容をDocumentFragmentにする
const range = document.createRange();
range.setStart(document.body, 0);
const after = range.createContextualFragment(message);
range.detach();
// DocumentFragmentの内容でbeforeと異なる部分があれば、
// それをbeforeに差分適用する
DOMUpdater.update(before, after); // ←これを作った。
});
Virtual DOMでなく生のReal DOMを更新内容として指定する(※例ではDocumentFragmentを使ってますが、普通のElementでも構いません。)ので、Virtual DOMの独自記法を覚えなくていいです。利点はそれだけです。
既に同じ事をするライブラリが世の中にはあったのかもしれませんが、自分には見つけられませんでした。どなたかご存じでしたら教えてください……
→と書いていたら、morphdomという似た趣旨のライブラリが既にあると教えて頂きました。今回実装したものとの比較を最後に追記しました。
Tree Style TabというFirefox用アドオンで、「他のアドオンから指示して、タブの中に任意のUI要素を追加する」という事をやるために作りました。
見た目を元々のタブに合わせているのでちょっと分かりにくいですが、このスクリーンショットの左側で「Add-ons - Mozilla | MDN」というラベルを伴って表示されている「細いタブっぽい物」が、別のアドオンから指示された通りの内容を、このライブラリによる差分適用で埋め込んだ部分です。
アドオン間での通信ではJSONオブジェクト形式のメッセージしか扱えないため、こういう事をやろうとすると
ということを決める必要があります。
DOMの変更の差分適用といえば既存のVirtual DOM専用のライブラリは既にいくつもあって、
このあたりの記法をそのまま使えばいいといえばいい話です。
が、どれもべつに「スタンダード」というわけではないようなので、どれを選んでも後で文句を言われそうな気がします。宗教戦争がもしあるなら、そこに参戦したくはないですし、ただでさえ「Tree Style Tabが他のアドオン向けに提供する独自のAPI」というめちゃめちゃニッチな場面なので、こんな限定された場面のために新たに(もし普段から使っている物があるなら、それとは別のライブラリ由来の)独自の記法を覚えてもらうのは忍びないです。というか、自分がこれ以上覚えたくありません。
その点、HTMLのソースを文字列で指定してDOMの標準的な機能でNodeやDocumentFragmentにするという事にしておけば、多少冗長ではあるものの、「デジュールスタンダードなんで」と言ってしまえます。技術選択で悩まなくてもよくするためだけの選択というわけです。
うちは大きなベッドを1つ使うのではなくシングルサイズのベッドを2つ並べて使っているのですが、引っ越しを機にベッドを買い換えたら困った事になり、工作と裁縫で解決しました。という話です。