{{MediaDevices/getDisplayMedia()}} などの既存のメカニズムは、Webアプリケーションが 画面キャプチャを開始することを可能にします。ユーザーがタブをキャプチャすることを選択した場合、 [[mediacapture-region|Region Capture]] などのメカニズムは、結果として得られるビデオトラックを 変化させ、それ以降に生成されるすべてのフレームに対して操作を実行します。([[mediacapture-region|Region Capture]] の例では、その操作は、対象要素のバウンディングボックスとフレームの交差部分に フレームを切り抜くことから成ります。)
Element Capture は、私たちが「制限 (restriction)」と名付ける新しい変化メカニズムを導入します。 アプリケーションがビデオトラックを特定の対象要素に「制限」すると、制限されたビデオトラックで 生成されるフレームは、その対象要素とその子孫からの情報のみで構成されます。言い換えれば、 トラックは対象要素をルートとするDOMサブツリーのキャプチャになります。
[[mediacapture-region|Region Capture]] は、アプリケーションがキャプチャを切り抜くことを
可能にします。ある要素 TARGET が制限対象であると仮定します。
TARGET のDOM子孫ではない他の要素が、TARGET の前面に描画される場合は
どうなるでしょうか。[[mediacapture-region|Region Capture]] を使用すると、これらの他の要素も
キャプチャされてしまいますが、これは必ずしも望ましいことではありません。
TARGET のバウンディングボックスに切り抜きつつ、DOM子孫ではないコンテンツを
キャプチャから除外できるメカニズムが求められています。
「エディタ」Webアプリケーション (テキストエディタ、画像エディタ、スライドエディタ、または ビデオエディタ) を考えてみましょう。このようなアプリケーションは多くの場合、メインコンテンツ領域を 含み、その周囲にはローカルユーザーがメインコンテンツ領域のコンテンツを編集できるようにする さまざまなツールバー、ドロップダウンメニュー、ウィジェットが配置されています。
Webアプリケーションがメインコンテンツ領域のみを記録し、それをリモート参加者に「ライブ」で 送信したり、ディスクに記録したりしたい場合があります。このようなアプリケーションは、 メインコンテンツ領域以外のものに、ストレージ、帯域幅、またはリモート参加者の画面領域を 必ずしも費やしたくはないでしょう。
[[mediacapture-region|Region Capture]] などのメカニズムは、対象要素のバウンディングボックスへの 切り抜きに役立ちますが、ドロップダウンリストが一時的にその上に描画される場合はどうなるでしょうか。
ビデオ会議アプリケーションは、しばしば「タイル」を使用して自身を配置します。各リモート 参加者のビデオはタイルに表示されます。テキストエディタや画像編集アプリケーションのような 共同作業Webアプリケーションが別のiframeに読み込まれ、このiframeもタイルとして表示されると 仮定します。
一部のリモート参加者も同様に、専用のタイルに同じツールを読み込むでしょう。しかし、一部の ユーザーがそのツールを読み込むために必要な権限を持っていない場合はどうなるでしょうか。 あるいは、そのツールをサポートしていないプラットフォームから参加している場合はどうでしょうか。
その場合、ビデオ会議ソリューションは、ツールを正常に読み込んだ参加者の一人に、ツールを 読み込めないユーザーに対してそのツールのタイルを画面共有させることを選択し、彼らが少なくとも それを閲覧できるようにするかもしれません (操作はできませんが)。これは、 {{MediaDevices/getDisplayMedia()}} と [[mediacapture-region|Region Capture]] による 自己キャプチャを使用して行うことができます。
しかし、このようなソリューションにはいくつかの問題があります。他の要素が、一時的にせよ 恒久的にせよ、ツールタイルの上に描画される場合はどうなるでしょうか。例としては次のものが 挙げられます:
Element Capture メカニズムは2つの部分で構成されます:
私たちは2つの 制限状態 (restriction-states) を定義します。restricted と unrestricted です。ビデオトラックは常にどちらか一方の状態にあります。トラックは [=unrestricted=] の状態で開始し、{{BrowserCaptureMediaStreamTrack/restrictTo()}} が正常に 呼び出されると [=restricted=] に変わる場合があります。
本文書で提示される [=restriction mechanism=] ({{BrowserCaptureMediaStreamTrack/restrictTo}}) は、直接のノード参照ではなく {{RestrictionTarget}} トークンに依存しています。これにより、ある文書による、別の文書で 指定された対象要素への制限が可能になります。
{{BrowserCaptureMediaStreamTrack/cropTo()}} と {{BrowserCaptureMediaStreamTrack/restrictTo()}} はそれぞれ異なるトークン型 - {{CropTarget}} と {{RestrictionTarget}} - を使用するため、文書は自身をキャプチャする文書に付与する 能力を制限することが可能です。
RestrictionTarget は、意図的に空の不透明な識別子です。その目的は、 {{BrowserCaptureMediaStreamTrack/restrictTo}} に入力として渡されることです。
[Exposed=(Window,Worker), Serializable]
interface RestrictionTarget {
[Exposed=Window, SecureContext] static Promise<RestrictionTarget> fromElement(Element element);
};
サポートされている型の {{Element}} を指定して {{RestrictionTarget/fromElement}} を 呼び出すと、その {{Element}} が {{RestrictionTarget}} に関連付けられます。この {{RestrictionTarget}} は {{BrowserCaptureMediaStreamTrack/restrictTo}} への入力として 使用できます。私たちは、まだ アクティブ な 文書 内での {{RestrictionTarget.fromElement()}} の 呼び出しによって返されたものを、有効な RestrictionTarget として定義します。
指定された |element| を伴って {{RestrictionTarget/fromElement}} が呼び出されると、 ユーザーエージェントは |element| を入力として [=create a RestrictionTarget|RestrictionTarget を作成=] します。ユーザーエージェントは {{Promise}} |p| を返さなければなりません (MUST)。ユーザーエージェントは、新しい {{RestrictionTarget}} に関連付けられた状態の必要な内部伝播をすべて完了した後にのみ |p| を解決しなければならず (MUST)、その時点でユーザーエージェントは新しい {{RestrictionTarget}} を {{BrowserCaptureMediaStreamTrack/restrictTo}} への有効な パラメータとして受け取る準備ができていなければなりません (MUST)。
以前に {{RestrictionTarget/fromElement}} が呼び出された {{Element}} を複製する場合、 その複製はいかなる {{RestrictionTarget}} にも関連付けられません。後で複製に対して {{RestrictionTarget/fromElement}} が呼び出された場合、新しい {{RestrictionTarget}} が それに割り当てられます。
|element| を入力として create a RestrictionTarget するには、 以下の手順を実行します:
|restrictionTarget| を型 {{RestrictionTarget}} の新しいオブジェクトとします。
|restrictionTarget|.[[\Element]] を |element| に設定します。
{{RestrictionTarget}} オブジェクトはシリアライズ可能です。|value|、|serialized|、および ブール値 |forStorage| が与えられた場合の [=serialization steps=] は次のとおりです:
|forStorage| が true の場合、{{DOMException/name}} 属性が
{{"DataCloneError"}} の値を持つ新しい {{DOMException}} オブジェクトをスローします。
|serialized|.[[\RestrictionTargetElement]] を |value|.{{RestrictionTarget/[[Element]]}} に設定します。
|serialized| および |value| が与えられた場合の [=deserialization steps=] は次のとおりです:
|value|.{{RestrictionTarget/[[Element]]}} を |serialized|.[[\RestrictionTargetElement]] に設定します。
私たちは、{{MediaStreamTrack}} |T| が以下のすべての条件を満たす場合に限り、それを 制限可能な MediaStreamTrack (restrictable MediaStreamTrack) と呼びます:
true である。私たちは、{{Element}} |E| が以下のすべての条件を満たす場合に限り、それが 制限の対象となる資格を持つ (eligible for restriction) と言います:
|E| が スタッキングコンテキスト を形成する。
|E| が 3Dで平坦化されている。
|E| が バックドロップルート を形成する。
|E| がちょうど1つの ボックスフラグメント を持つ。
|E| が レンダリングされている。
これらの条件が成り立つことを保証するために、開発者は次のスニペットのようなCSSを 使用できます:
#target {
isolation: isolate; /* Forms a stacking context. */
transform-style: flat; /* Flattened. */
}
私たちは、{{Element}} |E| が、以下のすべての条件が成り立つ場合に限り、{{MediaStreamTrack}} |T| に対する 有効な制限対象 (valid restriction target) であると言います:
非公式には、これは |T| がタブキャプチャに関連付けられたアクティブなビデオトラックであり、 |E| がキャプチャされたタブ内のDOMに [=connected=] された要素であることを意味します。
{{Element}} |E| が {{MediaStreamTrack}} |T| に対する [=valid restriction target=] であるか どうかは、キャプチャの開始前または開始後、ならびに制限の開始前または開始後のいずれにおいても 変化する可能性があることに注意してください。例としては次のものが挙げられます:
無効性は、有効性が回復するまで追加のフレームを抑制します。
[[mediacapture-region|Region Capture]] は {{BrowserCaptureMediaStreamTrack}} インターフェースを 導入しました。私たちはそれを新しいメソッド {{BrowserCaptureMediaStreamTrack/restrictTo}} で 拡張します。
[Exposed = Window]
partial interface BrowserCaptureMediaStreamTrack {
Promise<undefined> restrictTo(RestrictionTarget? RestrictionTarget);
};
以下でキューに入れられるすべてのタスクは、{{BrowserCaptureMediaStreamTrack}} と同じ グローバルオブジェクト に関連付けられた レンダリングタスクソース を使用します。
このメソッドの呼び出しは、ビデオトラックの制限を開始/停止するようユーザーエージェントに 指示します。
|restrictionTarget| を最初のパラメータとして呼び出された場合、ユーザーエージェントは 以下のアルゴリズムを実行しなければなりません (MUST):
[=this=] が [=restrictable MediaStreamTrack=] でない場合、新しい {{NotSupportedError}} で [=rejected=] された {{Promise}} を返します。
以下の手順を並行して実行します:
|E| を |restrictionTarget|.{{RestrictionTarget/[[Element]]}} とします。
[=this=] ビデオトラックの crop-state を uncropped に 更新します。
|restrictionTarget| に応じて [=this=] ビデオトラックの [=restriction-state=] を更新します:
このメソッド呼び出し前のトラックの状態を |preState| と呼び、このメソッド 呼び出し後の状態を |postState| と呼びます。ユーザーエージェントは、 |preState| に従って [=restricted=] (または [=unrestricted=]) された これ以上のフレームがアプリケーションに配信されないことが保証され、かつ アプリケーションに配信される追加のフレームがすべて、|postState| または それ以降の状態に従って [=restricted=] (または [=unrestricted=]) される ことになる場合に、|p| を解決するために グローバルタスクをキューに入れ なければなりません (MUST)。
ユーザーエージェントが、指定された対象 |restrictionTarget| に [=restricted=] されたビデオトラック |T| のために新しい |frame| を生成しようとするときは常に、ユーザーエージェントは以下のアルゴリズムを 実行しなければなりません (MUST):
最終ステップで生成されるフレームは、|E| とその子孫を無限の透明なキャンバスの上にレンダリングし、 装飾されたバウンディングボックス の 端がフレームの端とぴったり一致するように配置することで構築されます。
一部の実装では、フレームデータの基礎となるピクセルフォーマットがアルファチャンネル情報を 保持できない場合があります。この場合、実装はレンダリングされたフレームを黒 (`rgb(0,0,0)`) の無限のキャンバスとブレンドできます。
実装は、|E| のために生成された既存のビットマップデータを再利用するか、フレームのサイズで品質を 最大化するために要素の表示を再生成するかのいずれかを行うことができます (例えば、参照される要素が SVGフラグメントであることを実装が検出した場合)。ただし、フレームは、ラスタライズ品質の違いを 別として、上記でレンダリングされた |E| と同一に見えなければなりません。
キャプチャ対象側のコード:
const mainContentArea = navigator.getElementById('mainContentArea');
const restrictionTarget = await RestrictionTarget.fromElement(mainContentArea);
sendRestrictionTarget(restrictionTarget);
function sendRestrictionTarget(restrictionTarget) {
// Either send the restriction-target using postMessage(),
// or pass it on locally within the same document.
}
キャプチャを行う文書側のコード:
async function startRestrictedCapture(RestrictionTarget) {
const stream = await navigator.mediaDevices.getDisplayMedia();
const [track] = stream.getVideoTracks();
if (!!track.restrictTo) {
handleError(stream);
return;
}
await track.restrictTo(RestrictionTarget);
transmitVideoRemotely(track);
}
悪意のないアプリケーションにとって、本仕様によって導入されるAPIは純粋にプラスとなるはずです。 なぜなら、責任あるアプリケーションが記録される情報を削減することを可能にするからです。 これには好ましい特性があります。
例えば、既存のメカニズムを使用すると、ビデオ会議アプリケーションは次のことができます:
コンテンツをiframeに埋め込む。
現在のタブをキャプチャするようユーザーに促す。({{MediaDevices/getDisplayMedia()}} を 使用して。)
結果として得られるキャプチャを、キャプチャを意図したiframeだけに切り抜く。 ({{BrowserCaptureMediaStreamTrack/cropTo()}} を使用して。)
結果として得られるピクセルをリモート参加者に送信する。 (RTCPeerConnection を使用して。)
しかし、これはリスクを伴います。なぜなら、キャプチャを意図したコンテンツの前面に偶然描画される あらゆるコンテンツも、リモートに送信されてしまうからです。これがほんの一瞬であっても、 リモートユーザーが気づくかもしれません。そして、そのようなコンテンツは非常にプライベートな ものである可能性があります - 例えば、チャット通知、リマインダー、スピーカーノートなど。
本仕様で導入されるメカニズムは、責任あるアプリケーションが、そのような問題が起こり得ないことを 完全に保証する方法で自身を構成することを可能にします。そのようなアプリケーションは、ユーザーに 対するプライバシーの保証をより簡単に行い、維持することができます。
本仕様で導入されるメカニズムはすべて、自己キャプチャが他の何らかの手段 - 通常は {{MediaDevices/getDisplayMedia()}} - によって提供されることに依存しています。これらに関する 主な懸念は、アプリケーションにクロスオリジンコンテンツへの読み取りアクセスを許可することです。
悪意のあるアプリケーションがユーザーを騙して自己キャプチャを承認させると、その後、目に見えない iframeにクロスオリジンコンテンツを読み込み、そのコンテンツを前面に持ってくることができ、 ユーザーが反応する前に攻撃者がコンテンツを読み取ることを可能にします。このような攻撃は、 本仕様で導入されるメカニズムがなくても、すでに可能です。
主な懸念は、本仕様で導入するメカニズムが、上記で説明した古い攻撃ベクトルを 悪化させる べきではないということです。私たちが導入するメカニズムが、古い攻撃を ひそかに 実行することを可能にするのではないかと、当然ながら懸念されます。 私たちは、ここで導入されるメカニズムが攻撃者の攻撃を隠す能力を増大させるものではないと 主張します。そのような攻撃の隠蔽は、以下のいずれかの手法を使用して常に可能でした:
コンテンツを短時間表示する。 攻撃者は常に、単一フレームの時間幅だけ コンテンツを画面に一瞬表示することができました。これは記録するには十分な長さですが、 ユーザーが理解するには十分な長さではありません。
コンテンツを断片的に表示する。 攻撃者は常に、コンテンツを複数の小さな断片、 さらには各1ピクセルにまで分割し、それらを異なる場所と時間に表示することができました。 ユーザーはこの操作を観察できませんが、ソフトウェアがこれらのピクセルを収集して そこから画像を再構築するのは簡単なことです。
コンテンツを低い不透明度で表示する。 攻撃者は常に、ユーザーには知覚できないが 機械はまだ読み取れる不透明度でコンテンツを表示することができました。
これらの手法のいずれもそれ単体で十分ですが、それらを組み合わせることで、悪意のある アプリケーションは常に攻撃を効果的に隠蔽し、なおかつコンテンツを効率的に読み取ることが できました。
悪意のあるアプリが、そのコンテンツからのオプトインなしに、クロスオリジンiframe内の遮蔽物を 除去できるのではないかと懸念されるかもしれません。APIの形状はそのような攻撃を防ぎます - クロスオリジンiframeは {{RestrictionTarget}} を生成し、それを攻撃者となり得る者に 渡さなければなりません。{{RestrictionTargets}} は本仕様で導入されるAPIの一部としての 目的以外には役立たないため、{{RestrictionTarget}} の発行と受け渡しは、その遮蔽物の除去に 対するクロスオリジンiframeの許可を証明します。
本仕様で導入されるAPIの設計において、{{CropTarget}} を再利用せず、代わりに専用のトークン ({{CropTarget}}) を定義するという意識的な決定がなされました。これにより、以前に {{BrowserCaptureMediaStreamTrack/cropTo()}} を念頭に置いて設計および実装されたが、 {{BrowserCaptureMediaStreamTrack/restrictTo()}} は念頭に置いていなかった既存のWeb アプリケーションが、前のセクション で説明したように、 遮蔽物の除去を許可することへ実質的にオプトインしてしまうことがないようにしています。