DDDDbox Tech Blog

建築設計クラウドサービス『DDDDbox』のテックブログ

WebWorkerから出力したログはどこへ?

chrome
こんにちは、DDDDbox事業部で日々開発を行っている鈴木です。

以前、重たい3D幾何処理をWebAssembly(Wasm)で実装し、それをWeb Worker上で動かすということをやっていました。メインスレッドをブロックしたくないので、Workerに処理を逃がすというパターンです。

その際のバグ調査でWasm側の挙動を確認したくてログを仕込んだのですが、ChromeのDevToolsを開いてもログが出てきませんでした。

問題:Wasmのログが見えない

最初はWasm側の問題かと思い、web-sysクレートを入れ直してログを出すことを試しました。 しかし、ログは出ませんでした。

このときの構成は以下です。

flowchart TD
    A[React] -->|spawn| B[Web Worker]
    B -->|load, execute| C[WASM]
   
    C -->|postMessage| B
    B -->|postMessage| A

さらに調査を進めると、Web Worker側で console.log を呼んでも、ChromeのDevToolsにログが表示されないことがわかりました。

ここであれこれと小一時間悩んでしまいました。

解決:コンテキストの選択

結論から言うと、Chrome DevTools上で「コンテキスト」の選択をしていなかったことが原因でした。 ChromeのDevToolsのコンソール上部で、top を選択したまま「Selected context only」のチェックボックスがONになっていたため、Workerからのログが表示されていませんでした。

Chrome dev tool1

ここのチェックを外すと、Web Worker(JS側)のログや、Wasm(Rust側)からのログが表示されるようになりました。

プルダウンから top ではなく、該当のWeb Workerの項目を選択しても、ログが表示されるようになります。

とりあえずログが出たことで目的は果たしたのですが、そもそも context とは? top とは?という疑問が出たのでもう少し深掘りしてみることにしました。

「コンテキスト」とは

Chrome DevToolsでいうコンテキストとは何を指しているのか、調査しました。

これはBrowsing Context(閲覧コンテキスト)の概念と密接に関係しているようです。 MDNによると、閲覧コンテキストとは「ブラウザが文書(Document)を表示する環境」を指します。 具体的には、タブ、ウィンドウ、iframe、ポップアップなどはそれぞれ独自の閲覧コンテキストを持ちます。

閲覧コンテキストは独自のwindowオブジェクトを持ち、オリジン(origin)や履歴を個別に管理しています。 そのため、変数スコープが分かれており、互いに変数を直接共有することができません。

Chrome DevToolsのコンテキスト

Chrome DevToolsのコンテキスト選択のUIには、閲覧コンテキストに加えてWeb Workerも表示されます。

Web Workerは window オブジェクトを持つ閲覧コンテキストではなく、独自のグローバルスコープ(selfオブジェクト)を持つ実行環境として選択肢に含まれています。 例えば、Web Workerの中で window オブジェクトを使うとエラーになります。

また、Chrome拡張を入れていると、コンテキスト選択のUIに拡張機能の名前が出てきます。拡張機能のContent Scriptはページと同じDOMを共有していますが、JSの変数、つまりスコープは隔離されていて、別のコンテキストとして扱われます。

Chrome DevToolsのコンテキスト選択は、これらのコンテキストの中から、どのスコープの変数やログを操作・参照するか、を選ぶためのものとなっています。

「top」とは何か

ところで、DevToolsのコンテキスト表示のUIにデフォルト選択されている「top」ですが、何に対しての「トップ」なのでしょうか。

僕は最初、Linuxの top コマンドを連想していましたが、ちょっと意味合いが違うようです。

Chrome DevToolsのドキュメントには「top represents the main document's browsing context」と記載されています。つまり、「メインページの閲覧コンテキスト」を指しています。

歴史的背景

JavaScriptには古くから window.top というプロパティが存在します。 topはウィンドウ階層における最上位のウィンドウへの参照を返します。

// コンソールで試してみると
window.top === window  // true(iframeでなければ)
  • windowself): 自分自身
  • window.parent: 一つ上の親
  • window.top: 階層構造の最上位の閲覧コンテキスト

DevToolsの「top」という表示は、このwindow.topが指す最上位の閲覧コンテキストに由来していると思われます。

2000年代前半のWebでは、framesetタグを使って一つの画面の中に複数のHTMLを埋め込むことがありました。今でもiframeで広告や埋め込み動画を表示するときにその名残があります。

Top (メインページ)
├── iframe A (広告)
├── iframe B (ウィジェット)
│   └── iframe C (孫フレーム)

このような入れ子構造があったとき、「一番外枠の、URLバーに表示されているページのコンテキスト」を区別するために top という名前がついています。

Chrome DevToolsでの扱い

DevToolsのUI上は、top が全てのコンテキストの親要素になっていて、Web Workerはその子要素として表示されているので(並列ではなく)、ここがちょっとした混乱ポイントになっているかと思います。

top

Web WorkerやChrome拡張にとってのメインページは top なので、その考えではコンテキスト選択のUIは正しいです

しかし、Web WorkerやChrome拡張などは、top とはスコープが分かれており、その意味では並列に存在しています。 そのため、top を選択して 「Selected Context Only」の状態だと、Web Workerのログがフィルタされて表示されません。

また、top とライフサイクルが違うもの、例えば google.tag.managerの sw.js は コンテキスト選択のUI上もtop と並列に存在しています。

top に所属するものはタブを閉じれば top もろとも消えますが、top と並列に表示されているものはタブを閉じても、ブラウザの裏側で存在し続けます。

なぜコンテキストは分かれているのか

スコープを分けて名前の衝突を防ぐ理由としてはセキュリティがあります。

例えば、広告配信のスクリプトChrome拡張機能が、Webサイト本体の変数を上書きして壊さないようにするためであったり、また <iframe>の場合、異なるドメインスクリプトがお互いのクッキーやパスワードを盗み見られないように、壁を作る必要があります。いわゆるサンドボックスです。

DevToolsの「コンテキストセレクタ」が存在する理由は、開発者がデバッグするときに、壁の向こう側の変数を見たり操作したりするためです。

おまけ:Workerに名前をつけると便利

コンテキストの話ついでに知った小ネタです。

Web Workerを生成するとき、実は指定した名前をつけられます

// 第2引数で name オプションを指定
const myWorker = new Worker("worker.js", { name: "ImageProcessWorker" });

こうしておくと、DevToolsのコンテキスト一覧やネットワークタブで、ファイルパスのような名前ではなく、指定した ImageProcessWorkerという名前で表示されるようになります。

Workerを複数使うアプリだと、どのWorkerのログなのかわからなくなりがちなので、これは地味に便利だと感じました。

まとめ

  • Workerのログが出ないときは、Chrome DevToolsの「Selected context only」を疑う
  • コンテキストとは、変数の衝突を避けセキュリティを高める仕組み

DDDDboxでは、WebAssemblyやWeb Worker、WebGLなど、ブラウザの低レイヤーな技術も駆使した3D開発に取り組んでいます。こういった技術に興味があるエンジニアさんを大募集中です!

少しでもご興味をお持ちいただけましたら、カジュアルにお話するだけでも大丈夫ですのでお気軽にご連絡ください!

中途求人ページ: https://www.amd-lab.com/recruit-list/mid-career

カジュアル面談がエントリーフォームからできるようになりました。 採用種別を「カジュアル面談(オンライン)」にして必要事項を記載の上送信してください!

エントリーフォーム: https://www.amd-lab.com/entry