-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathindex.html
384 lines (382 loc) · 20.1 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
<!DOCTYPE html>
<html lang="en-us">
<head>
<title>Viewport Capture</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
<script src="https://www.w3.org/Tools/respec/respec-w3c" class="remove"></script>
<script class="remove" src="respec-config.js" type="text/javascript"></script>
</head>
<body>
<section id="abstract">
<p>
This document defines how a browser viewport can be used as the source of a media stream using
<code>getViewportMedia</code>, an extension to the Screen Capture API [[screen-capture]].
</p>
</section>
<section id="sotd">
<p>This document is not complete.</p>
</section>
<section class="informative" id="intro">
<h2>Introduction</h2>
<p>
This document describes an extension to the Screen Capture API [[screen-capture]] that enables the acquisition
of the browser viewport (the current tab), in the form of a video track. In some cases, tab audio is also
captured in the form of an audio track. This enables use cases such as: recording an ongoing WebRTC [[WEBRTC]]
video meeting, or a user in a video meeting sharing their presentation without having to locate it in a picker,
by instead clicking a button in their presentation application.
</p>
<p>
This feature is only available to "cross-origin isolated" documents. This prevents applications from using this
API to access potentially confidential information from other origins, content that should remain inaccessible
due to the protections offered by the user agent sandbox.
</p>
<p>
This feature has security implications, and requires a permission prompt. Sharing the rendered viewport may
expose user information such as browsing history (through link purpling), personal details like address or
payment info (through user agent or web extension features like form autofill), or personal preferences (like
font size).
</p>
</section>
<section id="conformance">
<p>
This specification defines conformance criteria that apply to a single product: the [=user agent =] that
implements the interfaces that it contains.
</p>
<p>
Implementations that use ECMAScript [[ECMA-262]] to implement the APIs defined in this specification must
implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification
[[!WEBIDL]], as this specification uses that specification and terminology.
</p>
</section>
<section>
<h2>Example</h2>
<p>
The following example demonstrates a request for viewport capture using the
<code>navigator.mediaDevices.getViewportMedia</code> method defined in this document.
</p>
<pre class="example highlight">
try {
const stream = await navigator.mediaDevices.getViewportMedia();
videoElement.srcObject = stream;
} catch (e) {
console.log('Unable to acquire viewport capture: ' + e);
}</pre
>
</section>
<section>
<h2>Terminology</h2>
<p>
This document uses the definitions of {{MediaStream}}, {{MediaStreamTrack}}, {{MediaStreamConstraints}} and
{{ConstrainablePattern}} from [[!GETUSERMEDIA]], and the definitions of
<a data-cite="SCREEN-CAPTURE#dfn-display-surface">display surface</a>
and <a data-cite="SCREEN-CAPTURE#dfn-browser">browser display surface</a>
from [[!screen-capture]].
</p>
</section>
<section>
<h2>Capturing Viewport Media</h2>
<p>
Capture of the viewport is enabled through the addition of a new {{MediaDevices/getViewportMedia}} method on the
{{MediaDevices}} interface, that is similar to {{MediaDevices/getDisplayMedia()}}, except it only captures the
top-level document's viewport (current tab), using a permission prompt instead of presenting the user with a
picker. For security reasons, it also only works from "cross-origin isolated" documents that opt-in with a
document-policy.
</p>
<section>
<h2><dfn>MediaDevices</dfn> Additions</h2>
<pre class="idl">
partial interface MediaDevices {
Promise<MediaStream> getViewportMedia(
optional DisplayMediaStreamOptions options = {});
};</pre
>
<dl data-link-for="MediaDevices" data-dfn-for="MediaDevices" class="methods">
<dt>
<dfn>getViewportMedia</dfn>
</dt>
<dd>
<p>Prompts the user for permission to live-capture the viewport (current tab).</p>
<p>
The user agent MUST apply any provided <var>options</var> to the produced media after permission has been
granted.
</p>
<p>
In the case of audio, the user agent MAY present the end-user with an option to include audio from the
current viewport in the capture, if available. Like {{MediaDevices/getDisplayMedia()}} with regards to
audio+video, the user agent is allowed to not return audio even if the audio constraint is present. If the
user agent knows no audio will be shared for the lifetime of the stream it MUST NOT include an audio track
in the resulting stream. The user agent MAY accept a request for audio and video by only returning a video
track in the resulting stream, or it MAY accept the request by returning both an audio track and a video
track in the resulting stream. The user agent MUST reject audio-only requests.
</p>
<p>
Like {{MediaDevices/getDisplayMedia()}}, the {{PermissionState/"granted"}} permission cannot be persisted.
</p>
<p>
When the {{MediaDevices/getViewportMedia()}} method is called, the user agent MUST run the following
steps:
</p>
<ol>
<li>
<p>
If the <a>current settings object</a>'s [=environment settings object/cross-origin isolated
capability=] is false, return a promise <a>rejected</a> with a {{DOMException}} object whose
{{DOMException/name}} attribute has the value {{SecurityError}}.
</p>
</li>
<li>
<p>
If the [=relevant global object=]'s [=associated `Document`=]'s [=top-level browsing context=]'s
<a href="https://wicg.github.io/document-policy/#required-document-policy">
required document policy</a
>
does not contain `Require-Document-Policy: viewport-capture` and `Document-Policy: viewport-capture`
(TODO: use correct algorithm), return a promise <a>rejected</a> with a {{DOMException}} object whose
{{DOMException/name}} attribute has the value {{SecurityError}}.
</p>
</li>
<li>
<p>
If the [=relevant global object=] of [=this=] does not have [=transient activation=], return a promise
<a>rejected</a> with a {{DOMException}} object whose {{DOMException/name}} attribute has the value
{{InvalidStateError}}.
</p>
</li>
<li>
<p>Let <var>options</var> be the method's first argument.</p>
</li>
<li>
<p>
If <code>options.video</code> is <code>false</code>, return a promise [=reject|rejected=] with a newly
[=exception/created=] {{TypeError}}.
</p>
</li>
<li>
<p>
For each [= map/exist | existing =] member in <var>options</var> whose value, <var>CS</var>, is a
dictionary, run the following steps:
</p>
<ol>
<li>
<p>
If <var>CS</var> contains a member named <code>advanced</code>, return a promise
[=reject|rejected=] with a newly [=exception/created=] {{TypeError}}.
</p>
</li>
<li>
<p>
If <var>CS</var> contains a member whose name specifies a constrainable property applicable to
<a data-cite="SCREEN-CAPTURE#dfn-display-surface">display surface</a>s, and whose value in turn is
a dictionary containing a member named either <code>min</code> or <code>exact</code>, return a
promise [=reject|rejected=] with a newly [=exception/created=] {{TypeError}}.
</p>
</li>
<li>
<p>
If <var>CS</var> contains a member whose name specifies a constrainable property applicable to
<a data-cite="SCREEN-CAPTURE#dfn-display-surface">display surface</a>s, and whose value in turn is
a dictionary containing a member named <code>max</code>, and that member's value in turn is less
than the constrainable property's <a data-cite="SCREEN-CAPTURE#dfn-floor-value">floor value</a>,
then let <var>failedConstraint</var> be the name of the member, let <var>message</var> be either
<code>undefined</code> or an informative human-readable message, and return a promise
[=reject|rejected=] with a new <code>OverconstrainedError</code> created by calling
<code>OverconstrainedError(<var>failedConstraint</var>, <var>message</var>)</code>.
</p>
</li>
</ol>
</li>
<li>
<p>
Let <var>requestedMediaTypes</var> be the set of media types in <var>options</var> with either a
dictionary value or a value of <code>true</code>.
</p>
</li>
<li>
<p>
If the [=relevant global object=]'s [=associated `Document`=] is NOT [=Document/fully active=] or does
NOT <a data-cite="!HTML/#gains-focus">have focus</a>, return a promise [=reject|rejected=] with a
{{DOMException}} object whose {{DOMException/name}} attribute has the value {{InvalidStateError}}.
</p>
</li>
<li>
<p>Let <var>p</var> be a new promise.</p>
</li>
<li>
<p>Run the following steps in parallel:</p>
<ol>
<li>
<p>For each media type <var>T</var> in <var>requestedMediaTypes</var>,</p>
<ol>
<li>
<p>
If no sources of type <var>T</var> are available, <a>reject</a> <var>p</var> with a new
{{DOMException}} object whose {{DOMException/name}} attribute has the value {{NotFoundError}}.
</p>
</li>
<li>
<p>
Read the current [= permission state=] for obtaining sources of type <var>T</var> in the
current browsing context. If the permission state is {{PermissionState/"denied"}}, jump to the
step labeled <em>PermissionFailure</em> below.
</p>
</li>
</ol>
</li>
<li>
<p>
Optionally, e.g., based on a previously-established user preference, for security reasons, or due
to platform limitations, jump to the step labeled <em>Permission Failure</em> below.
</p>
</li>
<li>
<p>
[=Request permission to use=] viewport capture, for a {{PermissionDescriptor}} with its
{{PermissionDescriptor/name}} set to <a>"viewport-capture"</a>, resulting in a set of provided
media.
</p>
<p>
The provided media MUST include precisely one video track, which MUST be a live-capture of the
<a data-cite="SCREEN-CAPTURE#dfn-browser">browser</a>
<a data-cite="SCREEN-CAPTURE#dfn-display-surface">display surface</a>
of the [=relevant global object=]'s [=associated `Document`=]'s [=top-level browsing context=]'s
<a data-cite="HTML#viewport">viewport</a>.
</p>
<p>
The provided media MUST include at most one audio track, which, if provided, MUST be the combined
audio produced by the sum of documents that consist of the [=relevant global object=]'s
[=associated `Document`=]'s [=top-level browsing context=]'s [=navigable/active document=], and
all [=navigable/active documents=] in nested [=browsing context=]s of the [=relevant global
object=]'s [=associated `Document`=]'s [=top-level browsing context=]. This audio track MUST NOT
be included if audio was not specified in <var>requestedMediaTypes</var>, or if it was specified
as <code>false</code>.
</p>
<p>The source of a {{MediaStreamTrack}} MUST NOT change.</p>
<p>
If the result of the request is {{PermissionState/"granted"}}, then for each device that is
sourcing the provided media, using a stable and private id for the device, <var>deviceId</var>,
set [[\devicesLiveMap]]<var>[deviceId]</var> to <code>true</code>, if it isn’t already
<code>true</code>, and set the [[\devicesAccessibleMap]]<var>[deviceId]</var> to
<code>true</code>, if it isn’t already <code>true</code>.
</p>
<p>The user agent MUST NOT store a {{PermissionState/"granted"}} permission entry.</p>
<p>
If the result is {{PermissionState/"denied"}}, jump to the step labeled
<em>Permission Failure</em> below. If the user never responds, this algorithm stalls on this step.
</p>
<p>
If the user grants permission but a hardware error such as an OS/program/webpage lock prevents
access, <a>reject</a> <var>p</var> with a new {{DOMException}} object whose {{DOMException/name}}
attribute has the value {{NotReadableError}} and abort these steps.
</p>
<p>
If the result is {{PermissionState/"granted"}} but device access fails for any reason other than
those listed above, <a>reject</a> <var>p</var> with a new {{DOMException}} object whose
{{DOMException/name}} attribute has the value {{AbortError}} and abort these steps.
</p>
</li>
<li>
<p>Let <var>stream</var> be the {{MediaStream}} object for which the user granted permission.</p>
</li>
<li>
<p>
Run the [=ApplyConstraints algorithm=] on all tracks in <var>stream</var> with the appropriate
constraints. Should this fail, let <var>failedConstraint</var>
be the result of the algorithm that failed, and let
<var>message</var> be either <code>undefined</code> or an informative human-readable message, and
then <a>reject</a> <var>p</var> with a new <code>OverconstrainedError</code>
created by calling
<code>OverconstrainedError(<var>failedConstraint</var>, <var>message</var>)</code>.
</p>
</li>
<li>Let <var>videoTrack</var> be the video track in <var>stream</var>.</li>
<li>Set <var>videoTrack</var>.{{MediaStreamTrack/[[Restrictable]]}} to <code>true</code>.</li>
<li>
<p><a>Resolve</a> <var>p</var> with <var>stream</var> and abort these steps.</p>
</li>
<li>
<p>
<em>Permission Failure</em>: [=Reject=] <var>p</var> with a new {{DOMException}} object whose
{{DOMException/name}} attribute has the value {{NotAllowedError}}.
</p>
</li>
</ol>
</li>
<li>
<p>Return <var>p</var>.</p>
</li>
</ol>
<p>
The <a>user agent</a> MUST NOT capture content that's behind a partially transparent captured
<a data-cite="SCREEN-CAPTURE#dfn-display-surface">display surface</a>.
</p>
<p>
The <a>user agent</a> MUST NOT share the audio other than audio emitted from the captured tab, and MUST
NOT share audio of the entire system.
</p>
</dd>
</dl>
</section>
<section>
<h2 id="constrainable-properties">Constrainable Properties for Captured Viewport Surfaces</h2>
<p>
The constraints relevant to {{MediaDevices/getViewportMedia}} are only those relevant to
{{MediaDevices/getDisplayMedia()}}, as defined in
<a data-cite="SCREEN-CAPTURE#constrainable-properties">
5.4 Constrainable Properties for Captured Display Surfaces</a
>.
</p>
</section>
<section data-cite="permissions">
<h2 id="permissions-intergration">Permissions Integration</h2>
<p>
<cite>Viewport Capture</cite> is a [=powerful feature=] which is identified by the [=powerful feature/name=]
<code><dfn class="permission">"viewport-capture"</dfn></code
>, requiring [=express permission=] to be used.
</p>
<p>
As required for integration with the [[[Permissions]]] specification, this specification defines the
following:
</p>
<dl>
<dt>[=powerful feature/permission state constraints=]</dt>
<dd>
Valid values for this descriptor's <a>permission state</a> are {{PermissionState/"prompt"}} and
{{PermissionState/"denied"}}. The user agent MUST NOT ever set this descriptor's <a>permission state</a> to
{{PermissionState/"granted"}}.
</dd>
</dl>
</section>
<section>
<h2 id="feature-policy-integration">Permissions Policy Integration</h2>
<p>
This specification defines a [=policy-controlled feature=] identified by the string <a>"viewport-capture"</a>.
Its [=policy-controlled feature/default allowlist=] is <code>"self"</code>.
</p>
<div class="note">
<p>
A [=document=]'s [=Document/permissions policy=] determines whether any content in that document is allowed
to use {{MediaDevices/getViewportMedia}}. If disabled in any document, no content in the document will be
[=allowed to use=] {{MediaDevices/getViewportMedia}}. This is enforced by the [=request permission to use=]
algorithm.
</p>
</div>
</section>
</section>
<section>
<h2>Privacy Indicator Requirements</h2>
<p>
This specification extends the
<a data-cite="SCREEN-CAPTURE#privacy-indicator-requirements"> Privacy Indicator Requirements</a> of
{{MediaDevices/getDisplayMedia()}} to include {{MediaDevices/getViewportMedia}}.
</p>
</section>
<section>
<h2>Security and Privacy Considerations</h2>
<p>
This section is informative; however, it notes some serious risks to platform security if the advice it contains
are not adhered to.
</p>
<p>TBD.</p>
</section>
</body>
</html>