第67話の操作室で、イトは反応しないbuttonを二度押さなかった。
listenerを一つ登録し、requestを一回だけ送り、返事を確かめてから二つのDOM nodeを更新した。
操作記録は、きれいにそろっている。
handler invocation 1
request 1
accepted response 1
DOM update batch 1
updated DOM nodes 2
duplicate request 0
それなのに、出口の大画面にはまだ灰色の 「待機中」 が残っていた。
イトの指が、もう一度buttonへ伸びる。
「画面が変わってない。やっぱり、もう一回動かさないと」
DOMは新しい。画面は古い
ユイが、画面の横にある二つの窓を開いた。
左は、いまブラウザが持っているDOM。
右は、高速カメラが最後に記録した画面だ。
current DOM
data-state = "ready"
status text = "受信完了"
last captured frame
data-state = "waiting"
status text = "待機中"
ユイ
DOMの文字は、もう変わっています。でも、最後に写った画面は一つ前の状態です
マコト
同じページを見ているのに、記録した時点が違うのか
イト
DOMが変わったら、その瞬間に画面も同じになるんじゃないの?
大画面の上に、赤い数字がともった。
次の案内開始まで、18秒。
その案内では、見学者全員が大画面を見て次の入口を選ぶ。灰色の「待機中」が残れば、進めない。
ただし、requestをもう一度送れば、返事が二重になるかもしれない。
隠すだけなら、壊れた場所を見失う。
イトは、伸ばした指をbuttonの手前で止めた。
三つのレバー
作業台から、三つのレバーがせり上がった。
A:handlerをもう一度動かす
requestからDOM更新まで、同じ処理をもう一周する。画面が変わる可能性はあるが、すでに成功した処理まで重複する。
B:古い表示の上に「完了」の札をかぶせる
見た目だけは間に合う。しかし、DOMと画面が食い違った理由は分からない。
C:DOMは一回の更新のまま、次のframeまでを追う
style、boxと配置、描画、画面に出たframeを別々に記録する。どこまで新しい状態が届いたかを確かめる。
ピコは答えを言わなかった。三つのレバーの下へ、小さな注意書きだけを置いた。
DOMの変更と、画面に見えるpixelは、同じ観測地点ではない。
残り14秒。
イトは、さっきの記録を見返した。handlerもrequestもDOM更新も、すでに一回成功している。
「失敗した証拠がない処理を、見た目だけで繰り返したくない」
イトはCのレバーを下ろした。
一回の更新を、次のframeまで追う
作業台が暗くなり、一本の青い軌道だけが残った。
軌道の始点は、更新後のDOM。
終点は、見学者が実際に見る画面だ。

最初の記録板には、枝分かれした構造が浮かんだ。
見出し。
状態を示す段落。
次へ進むlink。
HTMLをもとにブラウザが扱うこの構造が、DOMだ。
画面に文字が見える前から、DOMにはnodeとその関係がある。JavaScriptが今回変えたのは、このうち状態表示に関係する二つだけだった。
DOM batch 1
target nodes 2
extra DOM mutation 0
「ここまでは新しい」
イトは、始点へ青い印をつけた。
CSSは、誰に何を当てるか
次の記録板に、CSSのruleが流れ込んだ。
[data-state="ready"] .status {
color: #126a55;
font-weight: 700;
}
DOMにdata-state="ready"があるので、.statusへ新しい見た目の値が対応する。
ここで大切なのは、CSSの文字列をそのまま画面へ貼るのではないことだ。
どのruleがどのelementへ当たり、競合するruleのうち何が採用されたかを経て、そのelementで使う値が決まる。
この回の観測板では、それを computed style の地点として記録した。
computed color updated
computed font-weight updated
old style value 0
イト
DOMが新しくても、まずどの見た目になるかを確かめる地点があるんだ
ピコ
そう。DOMとCSSのruleと、画面で使われる値を一つの箱にしないのがコツだよ
DOMの枝が、そのまま四角になるわけではない
三つ目の記録板には、透明な四角が現れた。
statusの幅、高さ、x座標、y座標。
その外側のpanel。
隣のbuttonとの間隔。
ブラウザは、表示に使うboxを作り、大きさや位置を決めていく。
ただし、DOM nodeと画面のboxは一対一とは限らない。
見えない設定によってboxを作らないnodeもある。反対に、一つのelementが複数のboxに分かれることもある。
だからイトは、「DOMの枝をそのまま並べる」とは書かなかった。
ready status box present
width / height resolved
position resolved
layout overflow 0
前話で変えた文字は少し長い。それでもpanelからはみ出さず、次のlinkも押せる位置に残った。
残り8秒。
描くことと、見せたframeを分ける
四つ目の記録板で、透明だったboxへ色と文字が重なった。
背景を描く。
文字を描く。
境界線を描く。
必要なら、重なり合う内容をまとめる。
この回では、それらを paint と composite の観測地点に分けた。
けれど、これは仕組みを見分けるための工房のtraceだ。実際のbrowserで、内部の仕事が必ずこの札どおりの回数や順番でlogへ現れる、という意味ではない。
ブラウザは、表示を更新できる機会に、必要な仕事を進める。何も変わっていない場所まで、毎回すべて同じように作り直すとは限らない。
paint record for status 1
composited result ready
presented new frame 0
マコトが、最後の0を指した。
「描く準備は進んでる。でも、まだ新しいframeを見せていない」
高速カメラが古い「待機中」を写したのは、この直前だった。
DOM更新が失敗したのではない。
新しい表示を出す前のframeを、一枚だけ捕まえていた。
次の表示機会
残り5秒。
イトはbuttonへ戻らなかった。
新しいrequestも送らない。
DOMも変えない。
traceの終点にあるframe番号だけを見つめた。
captured old frame 417 waiting
next presented frame 418 ready
番号が一つ進んだ。
大画面の灰色が消え、深い緑の 「受信完了」 が現れた。次へ進むlinkも、同じframeで明るくなった。
案内開始まで、3秒。
見学者たちは迷わず次の入口を選んだ。
再実行しなかった結果
イトは、最初の操作記録とframe traceを一枚に重ねた。
handler invocation 1
request 1
accepted response 1
DOM update batch 1
extra DOM mutation 0
computed style update 1
layout overflow 0
paint record 1
presented new frame 1
duplicate request 0
ユイ
画面が古い一瞬を、処理全体の失敗と決めなかったから、重複を増やさずに済みました
マコト
DOM、style、box、描画、見せたframe。『画面になった』を観測地点に分けられたね
イト
見た目が変わらないとき、最初からやり直す前に、どこまでは新しいのかを確かめる。今回は、DOMをもう一度変える問題じゃなかった
ピコは最後に、短い式を残した。
DOMが新しい
≠ その瞬間に撮ったframeも新しい
一回の更新を保つ
+ 観測地点を分ける
= 重複させずに次のframeまで追える
画面は残ったのに、返事だけ来ない
案内が始まり、大画面は正しく描かれた。
そのとき、工房の外で光の線が一本消えた。
イトの端末には、いま描いた「受信完了」の画面が残っている。端末とWi-Fiルーターを結ぶ印も明るい。
ところが、次の時刻を取りに行ったrequestには、新しいresponseが返ってこなかった。
local screen visible
Wi-Fi link connected
new outside response none
イトは、今度はbuttonにもDOMにも触れなかった。
端末の中で画面になる仕事と、工房の外から返事が届く道は、同じ場所ではない。
「画面は見える。Wi-Fiもつながってる。それでも外から届かないなら、次はどの区間を見ればいい?」
ピコが扉を開くと、ルーターの先から世界中へ伸びる大きな地図が現れた。
次回、ピコルート第69話。
インターネットは何をつないでいる?

