Abstract

This document proposes a mechanism by which an application APP can opt-in to exposing certain information with another application CAPTR, if CAPTR is screen-capturing the tab in which APP is running.

Problem Description

Generic Problem Description

Consider a web-application, running in one tab, which we’ll name "main_app." Assume main_app calls getDisplayMedia and the user chooses to share another tab, where an application is running which we’ll call "captured_app."

Note that:

  1. main_app does not know what it is capturing.
  2. captured_app does not know that it is being captured; let alone by whom.

Both these traits are desirable for the general case, but there exist legitimate use cases where the browser would want to allow applications to opt-in to bridging that gap and enable a connection.

We wish to enable the legitimate use cases while keeping the general case as it was before.

Use-case #1: Cross-App Communications

Consider two applications that wish to cooperate, for example a VC app and a presentation app. Assume the user is in a VC session. The user starts sharing a presentation. Both applications are interested in letting the VC app discover that it is capturing a slides session, which application, and even which session, so that the VC application will be able to expose controls to the user for flipping through slides. When the user clicks those controls, the VC app will be able to send messages to the presentation app (either through a service worker or through a shared back-end infrastructure). These messages will instruct the presentation app to flip through slides, enter/leave presentation-mode, etc.

Use-case #2: Analytics

Capturing applications often wish to gather statistics over what applications their users tend to capture. For example, VC applications would like to know how often their users share presentation applications from specific providers, Wikipedia, CNN, etc. Gathering such information can be used to improve service for the users by introducing new collaborations, such as the one described above.

Use-case #3: Detecting Unintended Captures

Users sometimes choose to share the wrong tab. Sometimes they switch to sharing the wrong tab by clicking the share-this-tab-instead button by mistake. A benevolent application could try to protect the user by presenting an in-app dialog for re-confirmation, if they believe that the user may have made a mistake.

Use-case #4: Avoiding "Hall of Mirrors"

This use-case is a sub-case of #3, but deserves its own section due to its importance. The "Hall of Mirrors" effect occurs when users choose to share the tab in which the VC call takes place. When detecting self-capture, a VC application can avoid displaying the captured stream back to the user, thereby avoiding the dreaded effect.

The Capture-Handle Mechanism

The capture-handle mechanism consists of two main parts - one on the captured side, one on the capturing side.

Captured Side

Applications are allowed to expose information to capturing applications. They would typically do so before knowing if they even are captured. The mechanism used is calling {{MediaDevices/setCaptureHandleConfig}} with an appropriate {{CaptureHandleConfig}}.

CaptureHandleConfig

The CaptureHandleConfig dictionary is used to instruct the user agent what information the captured application intends to expose, and to which applications it is willing to expose said information.

          dictionary CaptureHandleConfig {
            boolean exposeOrigin = false;
            DOMString handle = "";
            sequence<DOMString> permittedOrigins = [];
          };          
        
exposeOrigin

If true, the user agent MUST expose the captured application's origin through the {{CaptureHandle/origin}} field of {{CaptureHandle}}. If false, the user agent MUST NOT expose the captured application's origin.

handle

The user agent MUST expose this value as {{CaptureHandle/handle}}.

Note: Values to this field are limited to 1024 16-bit characters. This limitation is specified further in {{MediaDevices/setCaptureHandleConfig}}.

permittedOrigins

Legal values of this field include:

  • The empty list.
  • A list with the single item "*"
  • A list consisting of valid origins.

If {{CaptureHandleConfig/permittedOrigins}} consists of the single item "*", then the {{CaptureHandle}} is observable by all capturers. Otherwise, {{CaptureHandle}} is [=observable=] only to capturers whose origin is lists in {{CaptureHandleConfig/permittedOrigins}}.

MediaDevices.setCaptureHandleConfig

{{MediaDevices}} is extended with a method - {{MediaDevices/setCaptureHandleConfig}} - which accepts a {{CaptureHandleConfig}} object. By calling this method, an application informs the user agent which information it permits capturing applications to observe.

          partial interface MediaDevices {
            undefined setCaptureHandleConfig(optional CaptureHandleConfig config = {});
          };
        
setCaptureHandleConfig

The user agent MUST run the following validations:

  • If {{CaptureHandleConfig/handle}} is set to an invalid value, the user agent MUST reject by raising {{TypeError}}.
  • If {{CaptureHandleConfig/permittedOrigins}} is set to an invalid value, the user agent MUST reject by raising {{NotSupportedError}}.
  • If the call to {{MediaDevices/setCaptureHandleConfig()}} is not from the [=top-level browsing context=], the user agent MUST reject by raising {{InvalidStateError}}.

If all validations passed, the user agent MUST accept the new config. The user agent MUST forget any previous call to {{MediaDevices/setCaptureHandleConfig}}; from now on, the application's {{CaptureHandleConfig}} is config.

The [=observable=] {{CaptureHandle}} is re-evaluated for all capturing applications.

  1. For every capturing application for which the new [=observable=] {{CaptureHandle}} is different than prior to the call to {{MediaDevices/setCaptureHandleConfig}}, an event of type {{CaptureHandleChangeEvent}} must be fired with {{CaptureHandleChangeEvent/captureHandle}} set to the new [=observable=] {{CaptureHandle}}.
  2. The user agent MUST report the new [=observable=] {{CaptureHandle}} whenever {{MediaStreamTrack/getCaptureHandle}} is called.

Capturing Side

Capturing applications who are permitted to [=observable|observe=] a track's {{CaptureHandle}} have two ways of reading it.

  1. Reading the current value returned by {{MediaStreamTrack/getCaptureHandle}}.
  2. Registering an {{EventListener}} at {{MediaStreamTrack/oncapturehandlechange}}.

CaptureHandle

The user agent exposes information about the captured application to the capturing application through the {{CaptureHandle}} dictionary. Note that a {{CaptureHandle}} object MUST NOT be given to a capturing application that is not permited to [=observable|observe=] it.

          dictionary CaptureHandle {
            DOMString origin;
            DOMString handle;
          };
        
origin

If the captured application opted-in to exposing its origin (by setting {{CaptureHandleConfig/exposeOrigin}} to true), then the user agent MUST set {{CaptureHandle/origin}} to the origin of the captured application. Otherwise, {{CaptureHandle/origin}} is not set.

handle

The user agent MUST set this field to the value which the captured application set in {{CaptureHandleConfig/handle}}.

MediaStreamTrack.getCaptureHandle()

Extend {{MediaStreamTrack}} with a method called {{MediaStreamTrack/getCaptureHandle}}. When the {{MediaStreamTrack}} is a video track derived of screen-capture, {{MediaStreamTrack/getCaptureHandle}} returns the latest [=observable=] {{CaptureHandle}}. Otherwise it returns null.

          partial interface MediaStreamTrack {
            CaptureHandle? getCaptureHandle();
          };
        
getCaptureHandle

If the track in question is not a video track, or is not the result of a capture of a display surface, then the user agent MUST return null.

If the captured application did not set a {{CaptureHandleConfig}}, or if the last time it set it to the empty {{CaptureHandleConfig}}, then the user agent MUST return null.

The user agent MUST compare the origin of the capturing document to those which the captured application listed in {{CaptureHandleConfig/permittedOrigins}}. If the capturing origin is not permitted to [=observable|observe=] the {{CaptureHandle}}, then the user agent MUST return null.

If all previous validations passed, then the user agent MUST return a {{CaptureHandle}} dictionary with the values derived of the last {{CaptureHandleConfig}} set by the captured application.

On-Change Event

CaptureHandleChangeEvent

Whenever the [=observable=] {{CaptureHandle}} for a given capturing application changes, the user agent fires an event of type CaptureHandleChangeEvent. This can happen in the following cases:

  1. The captured application call {{MediaDevices/setCaptureHandleConfig()}} with a new {{CaptureHandleConfig}}. (Note that the new {{CaptureHandleConfig}} might or might not cause the [=observable=] {{CaptureHandle}} to change, e.g. if changing {{CaptureHandleConfig/permittedOrigins}}.)
  2. The captured application's [=top-level browsing context=] is navigated cross-document.
  3. The user agent switches the track to follow a new application.
            [Exposed=Window]
            interface CaptureHandleChangeEvent : Event {
              constructor(CaptureHandleChangeEventInit init);
              [SameObject] CaptureHandle captureHandle();
            };
          
captureHandle

The track's {{CaptureHandle}} at the time the event was fired, as [=observable=] by the capturing application. If not [=observable=] by the capturing application, all of {{CaptureHandle}}'s fields will be set to their default value - the empty {{DOMString}}.

CaptureHandleChangeEventInit

            dictionary CaptureHandleChangeEventInit : EventInit {
              CaptureHandle captureHandle;
            };
          
captureHandle

The track's {{CaptureHandle}} at the time the event was fired.

oncapturehandlechange

{{MediaStreamTrack}} is extended with an {{EventListener}} called {{oncapturehandlechange}}.

            partial interface MediaStreamTrack {
              attribute EventHandler oncapturehandlechange;
            };
          
oncapturehandlechange

{{EventHandler}} for events of type {{CaptureHandleChangeEvent}}.