Chrome DevTools での最上位レイヤのサポート

Chrome DevTools にトップレイヤ要素のサポートが追加され、デベロッパーはトップレイヤ要素を使用するコードを簡単にデバッグできるようになりました。

この記事では、トップレイヤ要素の概要、DevTools を使用してトップレイヤ コンテンツを可視化し、トップレイヤ要素を含む DOM 構造を把握してデバッグする方法、DevTools のトップレイヤ サポートの実装方法について説明します。

最上位レイヤと最上位レイヤ要素とは何ですか?

モーダルとして <dialog> を開くと、内部で具体的にどのような処理が行われますか?🤔

最上位のレイヤに配置されます。最上位レイヤのコンテンツは、他のすべてのコンテンツの上にレンダリングされます。たとえば、モーダル ダイアログは他のすべての DOM コンテンツの上に表示する必要があります。そのため、作成者が z インデックスを手動で調整する必要がないように、ブラウザはこの要素を「最上位レイヤ」に自動的にレンダリングします。最上位レイヤの要素は、Z-Index が最も高い要素の上に表示されます。

最上位レイヤは、最も高いスタッキング レイヤと表現できます。各ドキュメントには 1 つのビューポートが関連付けられているため、1 つの最上位レイヤも関連付けられています。トップレイヤには複数の要素を同時に配置できます。複数のアイテムが重複すると、最後に追加されたアイテムが上に重ねられます。つまり、最上位レイヤのすべての要素は、最上位レイヤの後入れ先出し(LIFO)スタックに配置されます。

ブラウザが最上位レイヤにレンダリングする要素は <dialog> 要素だけではありません。現在、最上位レイヤの要素は、ポップオーバーモーダル ダイアログ全画面モードの要素です。

次のダイアログの実装を調べてみましょう。

<main>
  <button onclick="window.dialog.showModal();">Open Dialog</button>
</main>
<dialog id="dialog"></dialog>

バックドロップにスタイルが適用されたダイアログを 2 つ示したデモを以下に示します(バックドロップについては後述します)。

背景とは

幸い、最上位レイヤ要素の下のコンテンツをカスタマイズする方法があります。

最上位レイヤのすべての要素には、バックドロップというCSS 疑似要素があります。

バックドロップは、ビューポートと同じサイズのボックスで、最上位レイヤの要素のすぐ下にレンダリングされます。::backdrop 疑似要素を使用すると、最上位レイヤの最上位の要素が、その下にあるすべての要素を隠したり、スタイル設定したり、完全に非表示にしたりできます。

複数の要素をモーダルにする場合は、ブラウザは最前面の要素のすぐ下に、他の全画面要素の上にバックドロップを描画します。

バックドロップのスタイルを設定する方法は次のとおりです。

/* The browser displays the backdrop only when the dialog.showModal() function opens the dialog.*/
dialog::backdrop {
    background: rgba(255,0,0,.25);
}

最初のバックドロップのみを表示するにはどうすればよいですか?

すべての最上位レイヤ要素には、最上位レイヤ スタックに属する背景があります。これらの背景は重なり合うように設計されているため、背景の不透明度が 100% でない場合、下の背景が見えてしまいます。

最上位レイヤ スタック内の最初の背景のみを表示する必要がある場合は、最上位レイヤ スタック内のアイテム ID を追跡することで実現できます。

追加された要素が最上位レイヤの最初の要素でない場合、要素が最上位レイヤに配置されたときに呼び出される関数は、::backdrophiddenBackdrop クラスを適用します。このクラスは、要素が最上位レイヤから削除されると削除されます。

以下のサンプルデモのコードをご覧ください。

DevTools の最上位レイヤのサポート設計

トップレイヤの DevTools サポートにより、デベロッパーはトップレイヤのコンセプトを理解し、トップレイヤ コンテンツの変化を可視化できます。これらの機能は、デベロッパーが以下を特定するのに役立ちます。

  • 常に最上位レイヤにある要素とその順序。
  • 任意の時点でスタックの一番上にある要素。

さらに、DevTools のトップレイヤのサポートにより、トップレイヤ スタック内の背景疑似要素の位置を可視化できます。ツリー要素ではありませんが、最上位レイヤの動作において重要な役割を果たし、デベロッパーにとって有用な要素です。

最上位レイヤのサポート機能を使用すると、次のことができます。

  1. いつでも、最上位レイヤ スタックにある要素を確認できます。最上位レイヤの表現スタックは、最上位レイヤから要素が追加または削除されると動的に変化します。
  2. 最上位レイヤのスタック内の要素の位置を確認します。
  3. ツリー内の最上位レイヤの要素または要素の背景疑似要素から、最上位レイヤ表現コンテナ内の要素または背景疑似要素に移動し、その逆も同様に移動します。

これらの機能の使用方法を見てみましょう。

最上位レイヤのコンテナ

最上位レイヤの要素を可視化するために、DevTools は要素ツリーに最上位レイヤ コンテナを追加します。終了タグ </html> の後に配置します。

このコンテナを使用すると、最上位レイヤ スタック内の要素をいつでも確認できます。最上位レイヤ コンテナは、最上位レイヤ要素とその背景へのリンクのリストです。最上位レイヤの表現スタックは、最上位レイヤから要素が追加または削除されると動的に変化します。

要素ツリーまたは最上位レイヤ コンテナ内の最上位レイヤ要素を見つけるには、最上位レイヤ コンテナ内の最上位レイヤ要素の表現から要素ツリー内の同じ要素へのリンクをクリックします。

最上位レイヤのコンテナ要素から最上位レイヤのツリー要素に移動するには、最上位レイヤのコンテナ内の要素の横にある [表示] ボタンをクリックします。

最上位のコンテナ リンクから要素にジャンプする。

最上位レイヤのツリー要素から最上位レイヤのコンテナ内のリンクに移動するには、要素の横にある最上位レイヤバッジをクリックします。

要素から最上位レイヤのコンテナ リンクにジャンプする。

最上位レイヤのバッジを含め、任意のバッジをオフにできます。バッジを無効にするには、任意のバッジを右クリックし、[バッジの設定] を選択して、非表示にするバッジの横にあるチェックボックスをオフにします。

バッジをオフにする。

最上位レイヤ スタック内の要素の順序

最上位のレイヤ コンテナには、スタックに表示される順序で要素が表示されますが、順序は逆になります。スタック要素の上部は、最上位レイヤ コンテナの要素リストの最後です。つまり、最上位レイヤのコンテナ リストの最後の要素が、ドキュメント内で現在操作可能な要素です。

ツリー要素の横にあるバッジは、要素が最上位レイヤに属するかどうかを示し、スタック内の要素の位置番号が含まれています。

このスクリーンショットでは、最上位レイヤのスタックは 2 つの要素で構成されており、2 番目の要素がスタックの上部にあります。2 番目の要素を削除すると、1 番目の要素が最上位に移動します。

スタック内の要素の順序。

最上位レイヤ コンテナの背景

前述のように、すべての最上位レイヤ要素には、backdrop という CSS 疑似要素があります。この要素はスタイル設定できるため、検査して表現を確認することもできます。

要素ツリーでは、背景要素は、属する要素の終了タグの前に配置されます。ただし、最上位レイヤ コンテナでは、バックドロップ リンクは、それが属する最上位レイヤ要素のすぐ上に表示されます。

バックドロップのスタック位置。

DOM ツリーの変更

DevTools で個々の DOM ツリー要素の作成と管理を行うクラスである ElementsTreeElement では、最上位レイヤのコンテナを実装するには不十分でした。

最上位レイヤのコンテナをツリー内のノードとして表示するため、DevTools ツリー要素ノードを作成する新しいクラスを追加しました。以前は、DevTools 要素ツリーを作成するためのクラスが、TreeElement ごとに DOMNode で初期化されていました。DOMNode は、backendNodeId などのバックエンド関連のプロパティを持つクラスです。backendNodeId はバックエンドで割り当てられます。

最上位レイヤのコンテナノード。最上位レイヤ要素へのリンクのリストがあり、通常のツリー要素ノードとして動作する必要があります。ただし、このノードは「実際の」DOM ノードではなく、バックエンドで最上位レイヤのコンテナノードを作成する必要はありません。

最上位レイヤを表すフロントエンド ノードを作成するため、DOMNode なしで作成される新しいタイプのフロントエンド ノードを追加しました。この最上位レイヤのコンテナ要素は、DOMNode を持たない最初のフロントエンド ノードです。つまり、フロントエンドにのみ存在し、バックエンドは認識しません。他のノードと同じ動作を実現するため、フロントエンド ノードの動作を担当する UI.TreeOutline.TreeElement クラスを拡張する新しい TopLayerContainer クラスを作成しました。

目的の配置を実現するため、要素をレンダリングするクラスは、<html> タグの次の兄弟として TopLayerContainer をアタッチします。

新しい最上位レイヤバッジは、要素が最上位レイヤにあることを示します。また、TopLayerContainer 要素内のこの要素のショートカットへのリンクとしても機能します。

初期設計

当初は、要素へのリンクのリストを作成せずに、最上位レイヤの要素を最上位レイヤ コンテナに複製する予定でした。DevTools で要素の子要素の取得がどのように機能するかが原因で、このソリューションは実装されませんでした。各要素には、子要素の取得に使用される親ポインタがあり、複数のポインタを持つことはできません。したがって、ツリーの複数の場所にすべての子を適切に展開して含めるノードは作成できません。一般に、このシステムは重複するサブツリーを想定して構築されていません。

妥協案として、フロントエンドの DOM ノードを複製するのではなく、それらのノードへのリンクを作成することにしました。DevTools の要素へのリンクの作成を担当するクラスは ShortcutTreeElement で、UI.TreeOutline.TreeElement を拡張しています。ShortcutTreeElement は他の DevTools DOM ツリー要素と同じ動作をしますが、バックエンドで対応するノードはなく、ElementsTreeElement にリンクするボタンがあります。最上位レイヤのノードへの各 ShortcutTreeElement には、DevTools の DOM ツリー内の ::backdrop 疑似要素の表現にリンクする子 ShortcutTreeElement があります。

初期設計:

初期設計。

Chrome DevTools Protocol(CDP)の変更

最上位レイヤのサポートを実装するには、Chrome DevTools プロトコル(CDP)の変更が必要です。CDP は、DevTools と Chromium 間の通信プロトコルとして機能します。

次のように追加する必要があります。

  • フロントエンドからいつでも呼び出せるコマンド。
  • バックエンド側からフロントエンドでトリガーするイベント。

CDP: DOM.getTopLayerElements コマンド

現在の最上位レイヤ要素を表示するには、最上位レイヤにある要素のノード ID のリストを返す新しい試験運用版 CDP コマンドが必要です。DevTools は、DevTools が開かれたときや最上位レイヤの要素が変更されたときに、このコマンドを呼び出します。コマンドは次のようになります。

  # Returns NodeIds of the current top layer elements.
  # Top layer renders closest to the user within a viewport, therefore, its elements always
  # appear on top of all other content.
  experimental command getTopLayerElements
    returns
      # NodeIds of the top layer elements.
      array of NodeId nodeIds

CDP: DOM.topLayerElementsUpdated イベント

最上位レイヤ要素の最新リストを取得するには、最上位レイヤ要素のすべての変更で試験運用版の CDP イベントをトリガーする必要があります。このイベントは、変更をフロントエンドに通知します。フロントエンドは DOM.getTopLayerElements コマンドを呼び出して、新しい要素リストを受け取ります。

このイベントは次のようになります。

  # Called by the change of the top layer elements.
  experimental event topLayerElementsUpdated

CDP の考慮事項

最上位レイヤの CDP サポートを実装する方法は複数ありました。検討した別の方法として、トップレイヤ要素の追加や削除についてフロントエンドに通知するだけでなく、トップレイヤ要素のリストを返すイベントを作成することも考えられました。

または、コマンドの代わりに 2 つのイベント(topLayerElementAddedtopLayerElementRemoved)を作成することもできます。この場合、要素を受信し、フロントエンドで最上位レイヤ要素の配列を管理する必要があります。

現在、フロントエンド イベントは getTopLayerElements コマンドを呼び出して、更新された要素のリストを取得します。イベントがトリガーされるたびに、変更の原因となった要素のリストまたは特定の要素を送信すれば、コマンドの呼び出しを 1 ステップ省略できます。ただし、この場合、フロントエンドはどの要素を push するかを制御できなくなります。

トップレイヤ ノードをリクエストするタイミングは、フロントエンドで決定したほうがよいと判断したため、このように実装しました。たとえば、UI で最上位レイヤが閉じられている場合や、要素ツリーがない DevTools パネルを使用している場合、ツリーの深い場所にある余分なノードを取得する必要はありません。