Compute Pressure Level 1
  
    The Compute Pressure API provides a way for websites to react to changes
    in the CPU pressure of the target device, such that websites can trade off
    resources for an improved user experience.
  
  
    For this specification to exit the CR stage,
    at least 2 independent implementations of every feature defined in this specification need to be documented in the implementation report.
  
  A Note on Feature Detection
  This section is non-normative.
  
    Feature detection is an established web development best practice. Resources on the topic are plentiful on- and
    offline and the purpose of this section is not to discuss it further, but rather to put it in the context of
    detecting hardware-dependent features.
  
  
    Consider the below feature detection examples:
  
  
  
  Concepts
  
    This specification defines the following concepts:
  
  
    Processing Units
    
      Computing devices consist of a multitude of different processing units such as the Central
      Processing Unit (CPU), the Graphics Processing Unit (GPU) and many specialized
      processing units. The latter are becoming popular such as ones designed to accelerate specific
      tasks like machine learning or computer vision.
    
  
  
    Pressure sources
    
      The specification currently defines the valid source types as
      global system thermals and 
      the central [=processing unit=], also known as the CPU.
      Future levels of this specification MAY introduce additional [=source types=].
    
    
      enum PressureSource { "cpu" };
    
    
      enum PressureSource { "cpu", "thermals" };
    
    
      The PressureSource enum represents the [=valid source types=]:
    
    
      
        - 
          {{PressureSource/"thermals"}} represents the global thermal state of the system.
          
        
- 
          {{PressureSource/"cpu"}} represents the average pressure of the central [=processing unit=]
          across all its cores.
        
Sampling and Reporting Rate
    
      The requested sampling interval represents the desired
      interval between samples to be obtained from the hardware,
      expressed in milliseconds.
    
    
      Interval and frequency are inverses of each other, so the
      [=requested sampling interval=] can also be expressed as a
      requested sampling rate in Hertz (cycles per second) by
      dividing 1000 by the [=requested sampling interval=] value.
    
    
      The sampling rate for a [=platform collector=] is defined as a rate
      at which the [=user agent=] obtains telemetry readings from the underlying platform,
      and it might differ from the pressure observers' [=requested sampling rates=].
      The rate is measured in Hertz (cycles per second).
    
    
      The reporting rate for a pressure observer is the rate at which it runs
      the [=data collection=] steps, and it will never exceed the [=sampling rate=].
    
    
      The [=sampling rate=] differs from the [=requested sampling rate=] when the
      [=requested sampling rate=] exceeds upper or lower sampling rate bounds
      supported or accepted by the underlying platform and [=user agent=]†.
    
    
      †The specification additionally obfuscates the rate as outlined
      in [[[#rate-obfuscation]]].
    
    
      In case the user didn't request a [=sampling rate=], the [=sampling rate=]
      is [=implementation-defined=].
    
    
  
 Platform primitives
  
    A pressure source is an abstract, [=implementation-defined=]
    interface to hardware counters or an underlying framework that provides
    telemetry data about a source type
    defined by {{PressureSource}}. A [=pressure source=] can make use of data
    fusion with data from additional sources if that provides more precise
    results.
  
  
    The telemetry data provided by a [=pressure source=] is represented in this
    specification as a pressure source sample, a [=struct=]
    consisting of the following [=struct/items=]:
    
      - 
        data: [=contributing
        factors=] obtained from the underlying hardware and operating system or,
        in the case of a [=virtual pressure source=], a {{PressureState}}
        
          and a {{double?}} representing the {{PressureRecord/ownContributionEstimate}}
        .
      
- 
        timestamp: the
        [=unsafe shared current time=] when [=pressure source sample/data=] was
        obtained.
        
      
    A [=pressure source=] has an associated latest sample, a [=pressure source
    sample=] or null. It is initially null.
  
  
    A platform collector is an abstract interface responsible for
    obtaining telemetry samples from a [=pressure source=], transforming them
    into [=pressure states=] and providing them to the [=user agent=].
  
  
    A [=platform collector=] has the following associated data:
  
  
    - 
      an associated pressure
      source, which is a [=pressure source=] or null.
    
- 
      an activated boolean,
      initially false.
    
    The format of the telemetry data provided by a [=pressure source=]
    and stored in its [=pressure source/latest sample=]'s [=pressure source
    sample/data=] is [=implementation-defined=], and so is the process through
    which a [=platform collector=] transforms it into a [=pressure state=].
  
  
    For this specification's purposes, [=platform collectors=] are scoped to a
    [=global object=] via the [=platform collector mapping=].
  
  
    For automation purposes, a [=platform collector=] must have the ability to
    connect to [=virtual pressure sources=] and use their simulated [=pressure
    source sample/data=] as [=pressure states=] rather than raw platform data
    that must be transformed into an [=adjusted pressure state=].
  
  
    As collecting telemetry data often means polling hardware counters, it is not a free operation and thus,
    it should not happen if there are no one observing the data. See [[[#life-cycle]]] for more information.
  
  
    A [=platform collector=] samples data at a specific rate. A [=user agent=] may modify this rate
    (if possible) for privacy reasons, or ignore and fuse certain readings.
  
  
    User notifications
  
  
    It is RECOMMENDED that a [=user agent=] show some form of user-visible
    notification that informs the user when a pressure observer is active,
    as well as provides the user with the means to block the ongoing operation,
    or simply dismiss the notification.
  
  
    Policy control
  
  
    The Compute Pressure API defines a [=policy-controlled feature=]
    identified by the token "compute-pressure".
    Its [=policy-controlled feature/default allowlist=] is `'self'`.
  
  
    Workers (dedicated and shared) adhere to the permission policy set by their
    owning document(s).
  
  
    Shared workers often have multiple owning documents as they can be obtained
    by other documents with the [=same origin=].
    In this case, all owning documents must be [=allowed to use=] the [=policy-controlled
    feature=] defined by this specification.
  
  
    Dedicated workers can be created from other workers,
    in which case the permission policy of the first owning document
    (or owning documents, in case of a shared worker) up the owner
    chain will be used.
  
  
  
  
    Internal Slot Definitions
  
  
    Each [=global object=] has:
    
      - 
        a pressure observer task queued (a boolean), which is initially false.
      
- 
        a registered observer list per supported [=source type=], which is initially empty.
      
- 
        a platform collector mapping, an [=ordered map=] of [=source
        types=] to [=platform collectors=].
      
A registered observer consists of an observer (a {{PressureObserver}} object).
    The [=user agent=] has:
    
      - 
        a max queued records integer, which is set to an [=implementation-defined=] value, greater than 0.
      
- 
        a supported source types [=list=] of [=implementation-defined=] {{PressureSource}} values.
      
    A constructed {{PressureObserver}} object has the following internal slots:
  
  
    - 
      a [[\Callback]] of type {{PressureUpdateCallback}} set on creation.
    
- 
      a [[\PendingObservePromises]] [=list=] of zero or more source-promise [=tuples=], initially empty,
      where source holds a {{PressureSource}} string and promise holds a {{Promise}} object.
    
- 
      a [[\QueuedRecords]] [=queue=] of zero or more {{PressureRecord}}
      objects, which is initially empty.
    
- 
      a [[\LastRecordMap]] [=ordered map=] of {{PressureSource}} to
      the latest {{PressureRecord}}.
    
- 
      a [[\SampleIntervalMap]] [=ordered map=] of {{PressureSource}} to
      positive numbers. It represents the sample interval given source type.
    
    For the [=rate obfuscation=] mitigation the constructed {{PressureObserver}} object additionally
    has the following internal slots:
  
  
    - 
      an [[\ObservationWindow]] integer set as part of the [=reset observation window=] steps.
    
- 
      a [[\MaxChangesThreshold]] integer set as part of the [=reset observation window=] steps.
    
- 
      a [[\PenaltyDuration]] integer set as part of the [=reset observation window=] steps.
    
- 
      a [[\ChangesCountMap]] [=ordered map=], [=map/keyed=] on a {{PressureSource}},
      representing the [=source type=] that triggered transition to the current [=pressure state=].
      The [=ordered map=]'s [=map/value=] is an integer representing the number of state changes in the
      current observation window timeframe.
    
- 
      a [[\AfterPenaltyRecordMap]] [=ordered map=], [=map/keyed=] on a {{PressureSource}},
      representing the [=source type=] of the last {{PressureRecord}}.
      The [=ordered map=]'s [=map/value=] is a {{PressureRecord}}.
    
Pressure States
  
    Pressure states represents the minimal set of useful states that allows websites
    to react to changes in compute and system pressure with minimal degration in quality or service,
    or user experience.
  
  
    enum PressureState { "nominal", "fair", "serious", "critical" };
  
  
    The PressureState enum represents the [=pressure state=] with the following states:
  
  
    
      - 
        {{PressureState/"nominal"}}: The conditions of the target device are at an acceptable level with no noticeable
        adverse effects on the user.
      
- 
        {{PressureState/"fair"}}: Target device pressure, temperature and/or energy usage are slightly elevated, potentially
        resulting in reduced battery-life, as well as fans (or systems with fans) becoming active and audible.
        Apart from that the target device is running flawlessly and can take on additional work.
      
- 
        {{PressureState/"serious"}}: Target device pressure, temperature and/or energy usage is consistently highly elevated.
        The system may be throttling as a countermeasure to reduce thermals.
      
- 
        {{PressureState/"critical"}}: The temperature of the target device or system is significantly elevated and it requires
        cooling down to avoid any potential issues.
      
Contributing Factors
  
    Contributing factors represent the underlying hardware and operation system metrics contributing to the
    current [=pressure state=] and can be [=implementation-defined=].
    
      These may include information about how the metrics relate to operation system processes, allowing an
      implementation to estimate the site's contribution to the overall pressure.
    
  
  
    The adjusted pressure state is a [=pressure state=] determined by an [=implementation-defined=]
    algorithm that takes as input [=source type=] and any other [=implementation-defined=] data from
    [=contributing factors=]. This algorithm MUST not be deterministic to ensure [=break calibration=] mitigation effectiveness.
  
  
    The change in contributing factors is substantial steps are as follows:
    
      - 
        If [=implementation-defined=] low-level hardware metrics that contribute to the
        current [=pressure state=] drop below or exceed an, per metric,
        [=implementation-defined=] threshold
        for the current [=pressure state=], return true.
      
- 
        Return false.
      
Pressure Observer
The Compute Pressure API enables developers to understand the pressure
of system resources such as the CPU.
  
  
    callback PressureUpdateCallback = undefined (
      sequence<PressureRecord> changes,
      PressureObserver observer
    );
  
  This callback will be invoked when the [=pressure state=] changes.
 
  
    The {{PressureObserver}} can be used to observe changes in the [=pressure states=].
  
  
    [Exposed=(DedicatedWorker,SharedWorker,Window), SecureContext]
    interface PressureObserver {
      constructor(PressureUpdateCallback callback);
      Promise<undefined> observe(PressureSource source, optional PressureObserverOptions options = {});
      undefined unobserve(PressureSource source);
      undefined disconnect();
      sequence<PressureRecord> takeRecords();
      [SameObject] static readonly attribute FrozenArray<PressureSource> knownSources;
    };
  
  The PressureObserver interface represents a {{PressureObserver}}.
  
    The constructor() method
    
      The `new` {{PressureObserver(callback)}} constructor steps are:
      
        - 
          Set |this|.{{PressureObserver/[[Callback]]}} to |callback:PressureUpdateCallback|.
        
The observe() method
    
      The {{PressureObserver/observe(source, options)}} method steps are:
      
        - 
          Let |relevantGlobal| be [=this=]'s [=relevant global object=].
        
- 
          [=list/For each=] |document:Document| in |relevantGlobal|'s [=owning document set=]:
          
            - 
              If |document| is not [=allowed to use=] the [=policy-controlled
              feature=] token "compute-pressure", return [=a promise rejected with=] {{NotAllowedError}}.
            
 
- 
          Set [=this=].{{PressureObserver/[[SampleIntervalMap]]}}[|source|] to |options:PressureObserverOptions|'s {{PressureObserverOptions/sampleInterval}}.
        
- 
          Let |promise:Promise| be [=a new promise=].
        
- 
          Let |pendingPromiseTuple| be (|source|, |promise|).
        
- 
          [=list/Append=] |pendingPromiseTuple| to [=this=].{{PressureObserver/[[PendingObservePromises]]}}.
        
- 
          [=promise/React=] to |promise|:
          
            - 
              If |promise| was [=resolved|fulfilled=] or [=rejected=], then:
              
                - 
                  [=list/Remove=] |tuple| from [=this=].{{PressureObserver/[[PendingObservePromises]]}}.
                
 
 
- 
          Run the following steps [=in parallel=]:
          
            - 
              Let |platformCollector| be null.
            
- 
              If |relevantGlobal|'s [=platform collector mapping=]
              [=map/contains=] |source|:
              
                - 
                  Set |platformCollector| to |relevantGlobal|'s [=platform
                  collector mapping=][|source|].
                
 
- 
              Otherwise:
              
                - 
                  Let |newCollector| be a new [=platform collector=] whose
                  [=platform collector/associated pressure source=] is null.
                
- 
                  Let |virtualPressureSource:virtual pressure source or null|
                  be the result of invoking [=get a virtual pressure source=]
                  with |source| and |relevantGlobal|.
                
- 
                  If |virtualPressureSource| is not null:
                  
                    - 
                      If |virtualPressureSource|'s [=virtual pressure
                      source/can provide samples=] is true:
                      
                        - 
                          Set |newCollector|'s [=platform collector/associated
                          pressure source=] to |virtualPressureSource|.
                        
- 
                          [=set/Append=] |newCollector| to
                          |virtualPressureSource|'s [=virtual pressure
                          source/connected platform collectors=].
                        
 
 
- 
                  Otherwise:
                  
                    - 
                      Let |realPressureSource| be an [=implementation-defined=]
                      [=pressure source=] that provides telemetry data about
                      |source|, or null if none exists.
                    
- 
                      Set |newCollector|'s [=platform collector/associated
                      pressure source=] to |realPressureSource|.
                    
 
- 
                  If |newCollector|'s [=platform collector/associated
                  pressure source=] is not null:
                  
                    - 
                      Set |platformCollector| to |newCollector|.
                    
- 
                      Set |relevantGlobal|'s [=platform collector
                      mapping=][|source|] to |platformCollector|.
                    
 
 
- 
              If |platformCollector| is null, [=queue a global task=] on the
              [=PressureObserver task source=] given |relevantGlobal| to reject
              |promise| {{NotSupportedError}} and abort these steps.
            
- 
              Invoke [=activate data collection=] with |source| and
              |relevantGlobal|.
            
- 
              [=Queue a global task=] on the [=PressureObserver task source=] given
              |relevantGlobal| to run these steps:
              
                - 
                  If |promise| was rejected, run the following substeps:
                  
                    - 
                      If |relevantGlobal|'s [=registered observer list=] for |source| is [=list/empty=],
                      invoke [=deactivate data collection=] with |source| and |relevantGlobal|.
                    
- 
                      Return.
                    
 
- 
                  [=list/Append=] a new [=registered observer=] whose [=observer=] is [=this=]
                  to |relevantGlobal|'s [=registered observer list=] for |source|.
                
- 
                  Resolve |promise|.
                
 
 
- 
          Return |promise|.
        
The unobserve() method
    
      The {{PressureObserver/unobserve(source)}} method steps are:
      
        - 
          If |source:PressureSource| is not a [=supported source type=], throw {{"NotSupportedError"}}.
        
- 
          [=list/Remove=] from |this|.{{PressureObserver/[[QueuedRecords]]}} all
          |records| associated with |source|.
        
- 
          [=map/Remove=] |this|.{{PressureObserver/[[SampleIntervalMap]]}}[|source|].
        
- 
          [=map/Remove=] |this|.{{PressureObserver/[[LastRecordMap]]}}[|source|].
        
- 
          [=map/Remove=] |this|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}[|source|].
        
- 
          [=list/For each=] (|promiseSource|, |pendingPromise|) of [=this=].{{PressureObserver/[[PendingObservePromises]]}},
          if |source| is equal to |promiseSource|, [=reject=] |pendingPromise| with an {{AbortError}}.
        
- 
          Let |relevantGlobal| be [=this=]'s [=relevant global object=].
        
- 
          Let |registeredObserverList| be |relevantGlobal|'s [=registered
          observer list=] for |source|.
        
- 
          [=list/Remove=] any [=registered observer=] from
          |registeredObserverList| whose [=observer=] is [=this=].
        
- 
          If |registeredObserverList| is [=list/empty=]:
          
            - 
              Invoke [=deactivate data collection=] with |source| and
              |relevantGlobal|.
            
- 
              [=map/Remove=] |relevantGlobal|'s [=platform collector
              mapping=][|source|].
            
 
The disconnect() method
    
      The {{PressureObserver/disconnect()}} method steps are:
      
        - 
          [=list/Empty=] |observer|.{{PressureObserver/[[QueuedRecords]]}}.
        
- 
          [=map/Clear=] |this|.{{PressureObserver/[[SampleIntervalMap]]}}.
        
- 
          [=map/Clear=] |this|.{{PressureObserver/[[LastRecordMap]]}}.
        
- 
          [=map/Clear=] |this|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}.
        
- 
          [=list/For each=] (|promiseSource|, |pendingPromise|) of [=this=].{{PressureObserver/[[PendingObservePromises]]}},
          [=reject=] |pendingPromise| with an {{AbortError}}.
        
- 
          Let |relevantGlobal| be [=this=]'s [=relevant global object=].
        
- 
          [=map/For each=] |source| → |registeredObserverList| of
          |relevantGlobal|'s [=registered observer list=] [=ordered map=]:
          
            - 
              [=list/Remove=] any [=registered observer=] from
              |registeredObserverList| whose [=observer=] is [=this=].
            
- 
              If |registeredObserverList| is [=list/empty=]:
              
                - 
                  Invoke [=deactivate data collection=] with |source| and
                  |relevantGlobal|.
                
- 
                  [=map/Remove=] |relevantGlobal|'s [=platform collector
                  mapping=][|source|].
                
 
 
The takeRecords() method
    
    
      The {{PressureObserver/takeRecords()}} method steps are:
      
        - 
          Let |records| be a [=list/clone=] of |observer|.{{PressureObserver/[[QueuedRecords]]}}.
        
- 
          [=list/Empty=] |observer|.{{PressureObserver/[[QueuedRecords]]}}.
        
- 
          Return |records|.
        
The knownSources attribute
    
      The {{PressureObserver/knownSources}} getter steps are:
      
        - 
          Return [=user agent=]'s [=supported source types=] in alphabetical order.
        
The PressureRecord interface
  
    [Exposed=(DedicatedWorker,SharedWorker,Window), SecureContext]
    interface PressureRecord {
      readonly attribute PressureSource source;
      readonly attribute PressureState state;
      readonly attribute DOMHighResTimeStamp time;
      [Default] object toJSON();
    };
  
  
    partial interface PressureRecord {
      readonly attribute double? ownContributionEstimate;
    };
  
  
    A constructed  {{PressureRecord}} object has the following internal slots:
  
  
    - 
      a [[\Source]] value of type {{PressureSource}}, which represents the current [=source type=].
    
- 
      a [[\State]] value of type {{PressureState}}, which represents the current [=pressure state=].
    
- 
      
        an [[\OwnContributionEstimate]] value of type double?, which represents an estimated percentage
        (value between 0 and 1) of the current web site contribution to the global pressure for the current source.
        If the [=user agent=] cannot provide an estimate, the value will be null.
       
        For CPU pressure, this will include the pressure of the CPU core responsible for loading and running the
        web site, as well as any other associated CPU cores, in the case more than one are used. This can be the
        case with web workers and shared processes e.g., a compositor or networking process, which some
        [=user agents=] may use.
       
- 
      a [[\Time]] value of type {{DOMHighResTimeStamp}},
      which corresponds to the
      time the data was obtained from the system, relative to the [=environment settings object/time origin=] of the global object associated with
      the {{PressureObserver}} instance that generated the notification.
    
The source attribute
    
      The {{PressureRecord/source}} [=getter steps=] are to return its {{PressureRecord/[[Source]]}} internal slot.
    
  
  
    The state attribute
    
      The {{PressureRecord/state}} [=getter steps=] are to return its {{PressureRecord/[[State]]}} internal slot.
    
  
  
    The ownContributionEstimate attribute
    
      The {{PressureRecord/ownContributionEstimate}} [=getter steps=] are to return its {{PressureRecord/[[OwnContributionEstimate]]}} internal slot.
    
  
  
    The time attribute
    
      The {{PressureRecord/time}} [=getter steps=] are to return its {{PressureRecord/[[Time]]}} internal slot.
    
  
  
    The toJSON member
    
      When {{PressureRecord.toJSON}} is called, run [[[WebIDL]]]'s [=default toJSON steps=].
    
  
  The PressureObserverOptions dictionary
  
    dictionary PressureObserverOptions {
      [EnforceRange] unsigned long sampleInterval = 0;
    };
  
  
    The sampleInterval member
    
      The {{PressureObserverOptions/sampleInterval}} member represents the [=requested sampling
      interval=] expressed in milliseconds. In the case the value it set to 0, the system will
      only call the {{PressureUpdateCallback}} in the case there is a change to the {{PressureState}}.
    
    
  
  Life-cycle and garbage collection
  
    Each [=global object=] has a
    strong reference to [=registered observers=] in their [=registered observer list=]
    (one per source).
  
  
  Processing Model
  
    This section outlines the steps the user agent must take when implementing the specification.
  
  
    Supporting algorithms
    
      The reset observation window steps given the argument |observer:PressureObserver|, are as follows:
      
        - 
          set |observer|.{{PressureObserver/[[ObservationWindow]]}} to an [=implementation-defined=] randomized integer value in
          milliseconds within an [=implementation-defined=] range.
        
- 
          set |observer|.{{PressureObserver/[[MaxChangesThreshold]]}} to an [=implementation-defined=] randomized integer
          value of maximum allowed changes within the |observationWindow| within an [=implementation-defined=] range.
        
- 
          set |observer|.{{PressureObserver/[[PenaltyDuration]]}} to an [=implementation-defined=] randomized integer value
          in milliseconds, within an [=implementation-defined=] range.
        
- 
          [=list/Empty=] the observer.{{PressureObserver/[[ChangesCountMap]]}} map.
        
Run the [=reset observation window=] steps and start a timer to re-run the steps when the observer.{{PressureObserver/[[ObservationWindow]]}}
      time has passed, using different randomized values.
      
      
        To determine the owning document set for a [=relevant global object=] |relevantGlobal|:
        
          - 
            Let |owningDocumentSet| be an empty [=set=].
          
- 
            If |relevantGlobal| is {{Window}}, then [=set/append=] |relevantGlobal|'s [=associated document=] to |owningDocumentSet|.
          
- 
            Otherwise, [=list/for each=] |owner| in {{WorkerGlobalScope}} |relevantGlobal|'s [=WorkerGlobalScope/owner set=]:
            
              - 
                If |owner| is a {{Document}}, then [=set/append=] |owner| to |owningDocumentSet|.
              
- 
                If |owner| is a {{WorkerGlobalScope}}, set |owningDocumentSet| to the [=set/union=] of
                |owningDocumentSet| and |owner|'s [=owning document set=].
              
 
- 
              Return |owningDocumentSet|.
            
        The document has implicit focus steps given the argument |document:Document|, are as follows:
        
          - 
            If |document| is not [=Document/fully active=], return false.
          
- 
            Let |relevantGlobal| be |document|'s [=relevant global object=].
          
- 
            [=list/For each=] |origin| in
            
              initiators of active Picture-in-Picture sessions:
            
              - 
                If |relevantGlobal|'s [=relevant settings object=]'s [=origin=] is [=same origin=] with |origin|, return true.
              
 
- 
            If |relevantGlobal|'s [=browsing context=] is [=context is capturing|capturing=], return true.
          
- 
            Let |topLevelBC| be |relevantGlobal|'s [=browsing context=]'s [=top-level browsing context=].
          
- 
            If |topLevelBC| does not have [=top-level traversable/system focus=], return false.
          
- 
            Let |focusedDocument| be the |topLevelBC|'s
            
              currently focused area's [=Node/node document=].
          
- 
            If |relevantGlobal|'s [=relevant settings object=]'s [=origin=] is [=same origin=] with
            |focusedDocument|'s [=origin=], return true.
          
- 
            Otherwise, return false.
          
        The may receive data steps given the argument |observer:PressureObserver| are as follows:
        
          - 
            Let |relevantGlobal| be |observer|'s [=relevant global object=].
          
- 
            If |relevantGlobal| is a {{Window}} object:
            
              - 
                Return the result of running [=document has implicit focus=] with |relevantGlobal|'s [=associated Document=].
              
 
- 
            If |relevantGlobal| is a {{WorkerGlobalScope}} object:
            
              - Let |owningDocuments| be |relevantGlobal|'s [=owning document set=].
- 
                [=list/For each=] |document| in |owningDocuments|:
                
                  - 
                    If the result of running [=document has implicit focus=] with |document| is true,
                    return true.
                  
- 
                    Otherwise, [=iteration/continue=].
                  
 
 
- 
            Return false.
          
The passes rate test steps given the argument |observer:PressureObserver|,
      |source:PressureSource| and |timestamp:DOMHighResTimeStamp|, are as follows:
        - 
          If |observer|.{{PressureObserver/[[LastRecordMap]]}}[|source|] does not [=map/exist=], return true.
        
- 
          Let |record:PressureRecord| be |observer|.{{PressureObserver/[[LastRecordMap]]}}[|source|].
        
- 
          Let |sampleInterval| be |observer|.{{PressureObserver/[[SampleIntervalMap]]}}[|source|].
        
- 
          Let |timeDeltaMilliseconds:DOMHighResTimeStamp| = |timestamp| - |record|.{{PressureRecord/[[Time]]}}.
        
- 
          If |timeDeltaMilliseconds| ≥ |sampleInterval|, return true, otherwise return false.
        
The should dispatch steps given the argument |observer:PressureObserver|, |source:PressureSource|,
      |state:PressureState|, |ownContributionEstimate:double?|, are as follows:
        - 
          If |observer|.{{PressureObserver/[[SampleIntervalMap]]}}[|source|] > 0, return true.
        
- 
          If |observer|.{{PressureObserver/[[LastRecordMap]]}}[|source|] does not [=map/exist=], return true.
        
- 
          Let |record:PressureRecord| be |observer|.{{PressureObserver/[[LastRecordMap]]}}[|source|].
        
- 
          If |record|.{{PressureRecord/[[State]]}} is not equal to |state|, return true.
        
- 
          If |record|.{{PressureRecord/[[OwnContributionEstimate]]}} is not equal to |ownContributionEstimate|, return true.
        
- 
          Return false.
        
The passes rate obfuscation test steps given the argument |observer:PressureObserver|,
      |source:PressureSource|, are as follows:
        - 
          Increment observer.{{PressureObserver/[[ChangesCountMap]]}}[|source|].
        
- 
          Return observer.{{PressureObserver/[[ChangesCountMap]]}}[|source|]
          ≤ observer.{{PressureObserver/[[MaxChangesThreshold]]}}.
        
      To get a virtual pressure source, given a [=source type=]
      |source| and |relevantGlobal|, perform the following steps. They return a
      [=virtual pressure source=] or null.
      
        - Let |topLevelTraversable| be null.
- 
          If |relevantGlobal| is a {{Window}} object:
          
            - 
              Set |topLevelTraversable| to |relevantGlobal|'s [=Window/navigable=]'s [=top-level traversable=].
            
 
- 
          If |relevantGlobal| is a {{DedicatedWorkerGlobalScope}} object:
          
            - 
              Let |owningDocuments| be |relevantGlobal|'s [=owning document set=].
            
- 
              If |owningDocuments| is [=set/empty=], return null.
            
- 
              [=Assert=]: |owningDocuments|'s [=set/size=] is 1.
            
- 
              Set |topLevelTraversable| to |owningDocuments|[0]'s [=node navigable=]'s [=top-level traversable=].
            
 
- 
          If |topLevelTraversable| is null, return null.
        
- 
          Let |topLevelVirtualPressureSourceMapping| be the |topLevelTraversable|'s [=virtual pressure source mapping=].
        
- 
          Let |virtualPressureSource| be null.
        
- 
          If |topLevelVirtualPressureSourceMapping| [=map/contains=] |source|:
          
            - 
              Set |virtualPressureSource| to |topLevelVirtualPressureSourceMapping|[|source|].
            
 
- 
          Return |virtualPressureSource|.
        
Data Collection and Delivery
    
      To activate data collection given a [=source type=] |source|
      and |relevantGlobal|, perform the following steps:
    
    
      - 
        If |relevantGlobal|'s [=platform collector mapping=] does not
        [=map/contain=] |source|, abort these steps.
      
- 
        Let |platformCollector| be |relevantGlobal|'s [=platform collector
        mapping=][|source|].
      
- 
        If |platformCollector|'s [=platform collector/activated=] is true,
        abort these steps.
      
- 
        Set |platformCollector|'s [=platform collector/activated=] to true.
      
- 
        In an [=implementation-defined=] manner, start running the [=data
        collection=] steps with |relevantGlobal|, |source|, and
        |platformCollector|.
        
      
      To deactivate data collection given a [=source type=] |source|
      and |relevantGlobal|, perform the following steps:
    
    
      - 
        If |relevantGlobal|'s [=platform collector mapping=] does not
        [=map/contain=] |source|, abort these steps.
      
- 
        Let |platformCollector| be |relevantGlobal|'s [=platform collector
        mapping=][|source|].
      
- 
        If |platformCollector|'s [=platform collector/activated=] is false,
        abort these steps.
      
- 
        In an [=implementation-defined=] manner, stop running the [=data
        collection=] steps with |relevantGlobal|, |source|, and
        |platformCollector|.
      
- 
        Set |platformCollector|'s [=platform collector/activated=] to false.
      
- 
        If |platformCollector|'s [=platform collector/associated pressure
        source=] is a [=virtual pressure source=]:
        
          - 
            [=set/Remove=] |platformCollector| from its [=platform
            collector/associated pressure source=]'s [=virtual pressure
            source/connected platform collectors=].
          
 
- 
        Otherwise, perform any [=implementation-defined=] steps to signal to
        |platformCollector|'s [=platform collector/associated pressure source=]
        to stop retrieving telemetry data.
      
      The data collection steps given |relevantGlobal|, |source| and
      |platformCollector| are as follows:
      
        - 
          Let |pressureSource| be |platformCollector|'s [=platform
          collector/associated pressure source=].
        
- 
          If |pressureSource| is null, abort these steps.
        
- 
          Let |sample| be |pressureSource|'s [=pressure source/latest sample=].
        
- 
          If |sample| is null, abort these steps.
        
- 
          Let |state:PressureState| be null.
        
- 
          Let |ownContributionEstimate| be null.
        
- 
          If |pressureSource| is a [=virtual pressure source=]:
          
            - 
              Set |state| to the {{PressureState}} stored in |sample|'s [=pressure source sample/data=].
            
- 
              Set |ownContributionEstimate| to the {{double?}} stored in |sample|'s [=pressure source sample/data=].
            
 
- 
          Otherwise:
          
            - 
              If [=change in contributing factors is substantial=] is false, abort these steps.
            
- 
              Set |state| to an [=adjusted pressure state=] calculated from
              |source| and |sample|'s [=pressure source sample/data=].
              
            
- 
              If the [=user agent=] can calculate an |estimation| of the current site's
              contribution to the overall pressure in an [=implementation-defined=] manner,
              given |sample|'s [=pressure source sample/data=], then set |ownContributionEstimate|
              to that |estimation|. The |estimation| should be in the range [0...1].
            
 
- 
          [=Assert=]: |state| is not null.
        
- 
          Let |rawTimestamp| be |sample|'s [=pressure source
          sample/timestamp=].
        
- 
          Let |timeValue| be the [=relative high resolution time=] based on
          |rawTimestamp| and |relevantGlobal|.
        
- 
          [=list/For each=] |observer:PressureObserver| in |relevantGlobal|'s
          [=registered observer list=] for |source|:
          
            - 
              If running [=may receive data=] with |observer|
              returns false, [=iteration/continue=].
            
- 
              If running [=passes rate test=] with |observer|, |source| and |timeValue|
              returns false, [=iteration/continue=].
            
- 
              If running [=should dispatch=] with |observer|, |source|, |state|
               and |ownContributionEstimate|
              returns false, [=iteration/continue=].
            
- 
              Let |record:PressureRecord| be a new {{PressureRecord}} object with its
              {{PressureRecord/[[Source]]}} set to |source|,
              {{PressureRecord/[[State]]}} set to |state|,
              
                {{PressureRecord/[[OwnContributionEstimate]]}} set to |ownContributionEstimate|
              
              and {{PressureRecord/[[Time]]}} set to |timeValue|.
            
- 
              If |observer|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}[source] [=map/exists=]:
              
                - 
                  Set |observer|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}[|source|] to |record|.
                
- 
                  [=iteration/Continue=].
                
 
- 
              If running [=passes rate obfuscation test=] with |observer| and |source| returns false:
              
                - 
                  Set |observer|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}[|source|] to |record|.
                
- 
                  Set |observer|.{{PressureObserver/[[ChangesCountMap]]}}[|source|] to 0.
                
- 
                  Create timer of |observer|.{{PressureObserver/[[PenaltyDuration]]}} duration with the following callback:
                  
                    - 
                      If |observer|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}[source] [=map/exists=]:
                      
                        - 
                          Let |record| be |observer|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}[|source|].
                        
- 
                          [=map/Remove=] |observer|.{{PressureObserver/[[AfterPenaltyRecordMap]]}}[|source|].
                        
- 
                          Run [=queue a record=] with |observer|, |source|, |record|.
                        
 
 
- 
                  [=iteration/Continue=].
                
 
- 
              Run [=queue a record=] with |observer|, |source|, |record|.
            
 
Queue a PressureRecord
    
      To queue a record given the arguments |observer:PressureObserver|,
      |source:PressureSource|, |record:PressureRecord|,
      run these steps:
    
    
      - 
        If [=list/size=] of |observer|.{{PressureObserver/[[QueuedRecords]]}} is greater than
        [=max queued records=], then [=list/remove=] the first [=list/item=].
      
- 
        [=list/Append=] |record| to |observer|.{{PressureObserver/[[QueuedRecords]]}}.
      
- 
        Set |observer|.{{PressureObserver/[[LastRecordMap]]}}[|source|] to |record|.
      
- 
        [=Queue a pressure observer task=] with |observer|'s [=relevant global object=].
      
Queue a Pressure Observer Task
    
      The PressureObserver task source is a [=task source=] used for scheduling tasks to [[[#notify-observers]]].
    
    
      To queue a pressure observer task given |relevantGlobal| as input, run these steps:
    
    
      - 
        If the |relevantGlobal|'s [=pressure observer task queued=] is true, then return.
      
- 
        Set the |relevantGlobal|'s [=pressure observer task queued=] to true.
      
- 
        [=Queue a global task=] on [=PressureObserver task source=] with |relevantGlobal| to [=notify pressure observers=].
      
Notify Pressure Observers
    
      To notify pressure observers given |relevantGlobal| as input, run these steps:
    
    
      - 
        Set |relevantGlobal|'s [=pressure observer task queued=] to false.
      
- 
        Let |notifySet| be a new [=set=] of all [=observers=] in
        |relevantGlobal|’s [=registered observer lists=].
      
- 
        [=list/For each=] |observer:PressureObserver| of |notifySet|:
        
          - 
            Let |records| be a [=list/clone=] of |observer|.{{PressureObserver/[[QueuedRecords]]}}.
          
- 
            [=list/Empty=] |observer|.{{PressureObserver/[[QueuedRecords]]}}.
          
- 
            If |records| is not [=list/empty=], then [=invoke=] |observer|.{{PressureObserver/[[Callback]]}}
            with « |records|, |observer| » and "`report`".
          
 
Handling change of [=Document/fully active=] status
    
      This specification defines the following [=unloading document cleanup
      steps=] given a {{Document}} |document|:
    
    
      - 
        Let |relevantGlobal| be |document:Document|'s [=relevant global
        object=].
      
- 
        [=list/For each=] |source| [=map/key=] of |relevantGlobal|'s
        [=registered observer list=] [=ordered map=]:
        
          - 
            Invoke [=deactivate data collection=] with |source| and
            |relevantGlobal|.
          
- 
            [=map/Remove=] |relevantGlobal|'s [=platform collector
            mapping=][|source|].
          
 
      This specification previously included steps covering the case of a
      {{Document}} becoming [=Document/fully active=] again (i.e. integration
      with {{Document}}'s [=Document/reactivate=] steps). Those steps have been
      removed while the intended behavior is discussed.
    
  
  
    Handling changes to worker status
    
    
    
      Whenever a {{WorkerGlobalScope}} |relevantGlobal|'s
      [=WorkerGlobalScope/closing=] flag is set to true, perform the following
      steps:
    
    
      - 
        [=list/For each=] |source| [=map/key=] of |relevantGlobal|'s
        [=registered observer list=] [=ordered map=]:
        
          - 
            Invoke [=deactivate data collection=] with |source| and
            |relevantGlobal|.
          
- 
            [=map/Remove=] |relevantGlobal|'s [=platform collector
            mapping=][|source|].
          
 
    Security and privacy considerations
  
  
    Types of privacy and security threats
    
      The Working Group will list any known attack vectors, both theoretical and real-world,
      in this section.
    
    Timing attacks
    
      It may be possible to identify users across non-[=same origin=] sites if unique
      or very precise values can be accessed at the same time by sites not sharing
      origin.
      This attack is mitigated by [[[#data-minimization]]], [[[#rate-obfuscation]]],
      and [[[#same-origin-restriction]]].
    
    Cross-site covert channel
    
      In computer security a covert channel creates a capability to transfer information between processes
      that are not supposed to be allowed to communicate. In modern multi-process web engines in the generic
      case each window or tab resides in its own process (documents that have the [=same origin=] or sites that
      have the [=same site=] typically share the same process). Using this API it may be possible to create a
      cross-site covert channel C where a site A on one tab first broadcasts to the channel C after having
      manipulated the state of the CPU. Next a site B (that is not same site with site A) on another tab reads
      the broadcasted data from the channel C by using this API to learn when the state of the CPU has changed.
      This process is repeated as long as the scripts run on both the sites A and B.
    
    
      This attack is mitigated by [[[#rate-obfuscation]]] and [[[#break-calibration]]].
      Implementers are advised to consider all these mitigations for long-running scripts.
    
    
      The longer the scripts run the more information can be transmitted using the proposed cross-site covert channel.
      For example, if a user is on a video conferencing site and another long-running site that allows for more
      information to be transferred compared to a regular browsing scenario. On the other hand, a workload such as
      a video conferencing session will typically exert sustained pressure on the CPU that makes it harder to
      manipulate the pressure state in a predictive manner.
    
    Targeted de-anonymization attacks
    
      Targeted de-anonymization attacks constitute a critical class of threats that jeopardize a user's anonymity.
      These attacks allow a malicious or partially compromised website (referred to as the “malicious site”) to
      ascertain whether a website visitor possesses a specific public identifier, such as an email address or a
      social media handle.
    
    
      While anonymity may be a luxury for some, for certain individuals, it is far more than that—it is a matter
      of survival. Consider for instance those who engage in political protests, work as journalists covering
      sensitive topics, etc.
    
    
      As an example, an attacker can privately share a resource with the target for instance using a public
      resource sharing service (“victim site”), and then measure side-effects (indicating successful access)
      on loading the resource via side-channels. If the logged in visitor can access the embedded resource
      successfully, that indicates that the current visit is indeed the intended target.
    
    
      Specifically, exposing reliable information about the total CPU pressure can let an attacking site
      understand if a target of a cross-origin navigation (e.g. an iframe or pop-up window from another site)
      performed a CPU-intensive operation.
    
    
      Techniques such as pop-under and
      tab-under can be used to hide the loading
      from the user.
    
    
      One possible attack is that the malicious website opens e.g., a popup to a resource on a victim site
      to which the user is logged in (e.g. a video streaming site or online document editor) pointing to a
      resource shared with specific users.
    
    
      Assuming that loading the resource puts increased pressure on the CPU, this would create a side-channel
      reveals to the attacking site if the user is logged into an account with access to the resource,
      deanonymizing the user.
    
    
      Given that modern CPUs recover quickly from high pressure, one possible mitigation strategy could be to
      temporarily disable readings for a few seconds after loading popup and iframe content.
    
  
  
    Mitigation strategies
    
      This section gives a high-level view into mitigation strategies applicable to this specification.
      The normative definitions of these mitigations are integrated into the respective algorithms of this specification.
      Implementers are advised to consider the
      
TAG guidance
      on private browsing modes when implementing the mitigations defined in this specification.
    
Data minimization
    
      This specification adheres to the generic
      data minimization principles
      to limit exposure of data related to low-level details of the underlying platform to the minimum required to
      address its high-value use cases. This includes consideration for limiting exposure of
      identifying information about devices.
    
    
      The specific application of data minimization principles in the context of this specification
      are discussed in [[[#rate-obfuscation]]] and [[[#same-origin-restriction]]].
      
        Rate obfuscation
        
          The specification requires implementing the rate obfuscation mitigation
          to keep track of the number of pressure changes over an [=implementation-defined=]
          sliding observation window and
          set a flag if an [=implementation-defined=] threshold for the number of pressure
          changes is exceeded. Similarly, it is also recommended for the implementation to
          observe any abnormal activity such as a high number of pressure state changes
          spanning across multiple states, and set this flag similarly.
        
        
          If this flag is set, the implementation is recommended to give the pressure observer
          a penalty during which it will not be able to inform scripts of changes in its
          pressure state as it normally would. The duration of this penalty is
          [=implementation-defined=] and it is recommended to be randomized.
          When [=notify pressure observers=] resumes operation after the penalty, it only
          reports the latest pressure state and disregards any interim state information
          received from the platform collector during this penalty.
        
      
      
        Rate obfuscation normative parameters
        
          Based on implementation experience, implementers must use:
          
            - 
              a range in between 50 and 100 changes for PressureObserver's {{PressureObserver/[[MaxChangesThreshold]]}} internal slot.
            
- 
              a range in between 5000 milliseconds and 10000 milliseconds for PressureObserver's {{PressureObserver/[[PenaltyDuration]]}} internal slot.
            
      
      
      Rate obfuscation non-normative parameters
        This section is non-normative.
        
          Based on implementation experience, implementers are advised to use:
          
            - 
              a range in between 300000 milliseconds (5 minutes) and 600000 milliseconds (10 minutes) for PressureObserver's
              {{PressureObserver/[[ObservationWindow]]}} internal slot.
            
      
      
        Break calibration
        
          In a calibration process an attacker tries to manipulate the CPU so that this
          API would report a transition into a certain pressure state with the highest
          probability in response to the pressure exerted by the fabricated workload.
          This break calibration mitigation solution can slow down or prevent this calibration process
          from succeeding by slightly changing at runtime the [=implementation-defined=]
          low-level hardware metrics that contribute to these pressure state transitions.
          Even if the initial calibration would succeed, its results will be invalidated
          at runtime when this mitigation is running continuously. Any attempts to recalibrate
          will similarly be mitigated against.
        
        
      
      
        Break calibration parameters
        This section is non-normative.
        
          Based on implementation experience, implementers are advised to apply the mitigation
          to a randomized time value within a range between 120000 milliseconds (2 minutes) and 240000 milliseconds (4 minutes).
        
        
      
      
        Same-origin restriction
        
          By default data delivery is restricted to documents served from the same-origin as an
          
          initiator of an active picture-in-picture-session,
          documents [=context is capturing|capturing=]
          or the document with [=top-level traversable/system focus=], if any.
        
        
          The documents qualifying for data delivery, under the above rules, can delegate it to documents in [=child navigables=].
        
        
          The feature can be extended to third-party contexts such as iframes only by a
          declared policy.
        
        
          Shared workers can be shared across documents, such as top level document and those associated iframes. If one
          of the documents in the [=WorkerGlobalScope/owner set=] passes the above data delivery requirements, the shared worker will
          qualify for data delivery. This means that the embedded iframe is able to pass along the data to the embedding document.
        
      
    
  
  
    Accessibility considerations
  
  
    The Compute Pressure API is focused on improving the user experience. There are two ways in which applications that build on the API can positively impact accessibility.
  
  
    - 
      Considering users' access needs when making decisions based on information gathered using the API.
    
- 
      Designing and making user interfaces based on information gained from the API with accessibility in mind.
    
    As a consumer of the API, it's important to consider both of these opportunities. Here are some examples:
    
      - 
        Decision: In a video conferencing scenario, there may be multiple video streams. The system may determines that it needs to drop certain streams in order to conserve resources. If one of the video streams comes from a sign language interpreter, then that stream must be prioritized over others, so that the user can still understand the conversation. In practice, this could be simply implemented by allowing the user to "pin" a certain stream, and ensuring that that pinned stream is never automatically dropped by the system.
      
- 
        User Interface:
        
          - 
            A simple load-level meter, in which the current usage level bucket is indicated on the screen. This information must be conveyed using more than just color, so that people who cannot perceive color can still perceive the information. A symbol could be used in conjunction with color. Text could also be used in conjunction with both shape and color.
          
- 
            Some applications may present a notification to the user when some functionality is restricted due to compute pressure. These notifications may take the form of "toast" messages, in which case care must be taken to ensure that people using assistive technologies (including screen readers) can be made aware of the notification, and dismiss it, without unduly interrupting their workflow.
          
 
    Automation
  
  
    The Compute Pressure API poses a challenge to test authors, as fully
    exercising interface requires physical hardware devices that respond in
    predictable ways.
  
  
    To address this challenge this document defines a [[!WEBDRIVER2]] [=extension
    commands=] that allows defining and controlling virtual pressure sources that behave
    like real ones and which can have particular properties and whose readings can be
    entirely defined by users.
  
  
    Virtual Pressure Source
  
  
    A virtual pressure source is a [=pressure source=] that
    simulates the behavior of a real one in controlled ways. It reports
    pressure changes to zero or more [=platform collectors=] connected to it.
  
  
    Contrary to a real [=pressure source=], however, it reports [=pressure
    state=] values directly instead of [=implementation-defined=] values that
    must be processed into [=pressure states=] by a [=platform collector=]. In
    other words, a [=virtual pressure source=]'s [=pressure source sample=]'s
    [=pressure source sample/data=] is a {{PressureState}}.
  
  
    In addition to the data associated with all [=pressure sources=] (such as
    [=pressure source sample=]), each [=virtual pressure source=] has:
    
      - 
        a can provide samples
         boolean.
      
- 
        a connected platform
        collectors [=set=] of [=platform collectors=].
      
    Each [=top-level traversable=] has a virtual pressure source mapping, which is an [=ordered map=] of
    [=source types=] to [=virtual pressure source=].
  
  
  
  
    Extension Commands
  
  
    Create virtual pressure source
  
  
    
      | HTTP Method | [=extension command URI Template|URI Template=] | 
    
      | POST | /session/{session id}/pressuresource | 
  
  
    This [=extension command=] creates a new [=virtual pressure source=] of a specified
    [=source type=]. Calls to {{PressureObserver/observe()}} from {{PressureObserver}} instances
    of the same [=source type=] will cause this [=virtual pressure source=] to be used as their
    backing [=pressure source=] until [[[#delete-virtual-pressure-source]]] is run.
  
  
    
      Properties of the parameters argument used by this algorithm
    
    
      | Parameter name | Value type | Required | 
    
      | type | String | yes | 
    
      | supported | Boolean | no | 
  
  
    The [=remote end steps=]  given |session|, |URL variables| and |parameters| are:
  
  
    - 
      Let |virtualPressureSourceType| be the result of invoking
      get a property "type" from |parameters|.
    
- 
      If the [=user agent=]'s [=supported source types=] does not
      [=list/contain=] |virtualPressureSourceType|, return [=error=] with
      [=error code|WebDriver error code=] [=invalid argument=].
    
- 
      Let |topLevelTraversable| be the current browsing
      context's [=browsing context/top-level traversable=].
    
- 
      Let |topLevelVirtualPressureSourceMapping| be the |topLevelTraversable|'s [=virtual pressure source mapping=].
    
- 
      If |topLevelVirtualPressureSourceMapping| contains |virtualPressureSourceType|, return [=error=] with
      [=error code|WebDriver error code=] [=invalid argument=].
    
- 
      Let |supported| be the result of invoking get a
      property with default
      with "supported" and true from |parameters|.
    
- 
      Let |virtualPressureSource| be a new [=virtual pressure source=].
    
- 
      Set |virtualPressureSource|'s [=virtual pressure source/can provide
      samples=] to |supported|.
    
- 
      Set |topLevelVirtualPressureSourceMapping|[|virtualPressureSourceType|] to |virtualPressureSource|.
    
- 
      Return [=success=] with data null.
    
    Delete virtual pressure source
  
  
    
      | HTTP Method | [=extension command URI Template|URI Template=] | 
    
      | DELETE | /session/{session id}/pressuresource/{type} | 
  
  
    This [=extension command=] deletes a given [=virtual pressure source=], meaning that,
    if available, data for the given [=source type=] will be delivered the regular way, by non-virtual means.
  
  
    The [=remote end steps=] given |session|, |URL variables| and |parameters| are:
  
  
    - 
      Let |virtualPressureSourceType| be the value of the |URL variables|["type"].
    
- 
      If the [=user agent=]'s [=supported source types=] does not
      [=list/contain=] |virtualPressureSourceType|, return [=error=] with
      [=error code|WebDriver error code=] [=invalid argument=].
    
- 
      Let |topLevelTraversable| be the current browsing
      context's [=browsing context/top-level traversable=].
    
- 
      Let |topLevelVirtualPressureSourceMapping| be the |topLevelTraversable|'s [=virtual pressure source mapping=].
    
- 
      Let |pressureSource| be |topLevelVirtualPressureSourceMapping|[|virtualPressureSourceType|].
    
- 
      [=set/For each=] |platformCollector| of |pressureSource|'s [=virtual
      pressure source/connected platform collectors=]:
      
        - 
          Set |platformCollector|'s [=platform collector/associated pressure
          source=] to null.
        
 
- 
      [=map/Remove=] |topLevelVirtualPressureSourceMapping|[|virtualPressureSourceType|].
    
- 
      Return [=success=] with data null.
    
    Update virtual pressure source
  
  
    
      | HTTP Method | [=extension command URI Template|URI Template=] | 
    
      | POST | /session/{session id}/pressuresource/{type} | 
  
  
    This [=extension command=] allows updating the state of a [=virtual pressure source=] by pushing
    a new [=pressure source sample=].
  
  
  
    
      Properties of the parameters argument used by this algorithm
    
    
      | Parameter name | Value type | Required | 
    
      | sample | {{PressureState}} | yes | 
    
      | ownContributionEstimate | {{double}} | no | 
  
  
    The [=remote end steps=] given |session|, |URL variables| and |parameters| are:
  
  
    - 
      Let |virtualPressureSourceType| be the value of the |URL variables|["type"].
    
- 
      If the [=user agent=]'s [=supported source types=] does not
      [=list/contain=] |virtualPressureSourceType|, return [=error=] with
      [=error code|WebDriver error code=] [=invalid argument=].
    
- 
      Let |topLevelTraversable| be the current browsing
      context's [=browsing context/top-level traversable=].
    
- 
      Let |topLevelVirtualPressureSourceMapping| be the |topLevelTraversable|'s [=virtual pressure source mapping=].
    
- 
      If |topLevelVirtualPressureSourceMapping| does not [=map/contain=] |virtualPressureSource|,
      return [=error=] with [=error code|WebDriver error code=]
      unsupported operation.
    
- 
      Let |virtualPressureSource| be |topLevelVirtualPressureSourceMapping|[|virtualPressureSourceType|].
    
- 
      Let |sample| be the result of invoking
      get a property "sample" from |parameters|.
    
- 
      If |sample| is not of type {{PressureState}}, return [=error=] with [=error code|WebDriver error code=] [=invalid argument=].
    
- 
      Let |ownContributionEstimate| be the result of invoking
      get a property with default with arguments "ownContributionEstimate" and null from |parameters|.
    
- 
      If |ownContributionEstimate| is not of type {{double?}}, return [=error=] with [=error code|WebDriver error code=] [=invalid argument=].
    
- 
      Set |virtualPressureSource|'s [=pressure source/latest sample=] to a new
      [=pressure source sample=] whose [=pressure source sample/data=] is
      
        a tuple of |sample| and |ownContributionEstimate|,
      
      
        |sample|
      
      and [=pressure source sample/timestamp=] is the [=unsafe shared
      current time=].
    
- 
      In an [=implementation-defined=] way, make |virtualPressureSource|'s
      [=pressure source/latest sample=] available to |virtualPressureSource|'s
      [=virtual pressure source/connected platform collectors=].
    
- 
      Return [=success=] with data null.
    
    Examples
  
  
    const samples = [];
    function pressureChange(records, observer) {
      for (const record of records) {
        samples.push(record.state);
        // We only want 20 samples.
        if (samples.length == 20) {
          observer.disconnect();
          return;
        }
      }
    }
    const observer = new PressureObserver(pressureChange);
    observer.observe("cpu");
  
  
    In the following example we want to lower the number of concurrent video streams when the
    pressure becomes critical. For the sake of simplicity we only consider this one state.
  
  
    As lowering the amount of streams might not result in exiting the critical state,
    or at least not immediately, we use a strategy where we lower one stream at the time
    every 30 seconds while still in the critical state.
  
  
    We accomplish this by making sure the callback is called at least once every 30 seconds,
    or when the state actually changes. When the state changes we reset the interval timer.
  
  
    let timerId = -1;
    function pressureChange(records) {
      // Clear timer every time we are called, either by an actual state change,
      // or when called by setTimeout (see below).
      if (timerId > 0) {
        clearTimeout(timerId);
      }
      // When entering critical state, we want to recheck every 30sec if we are
      // still in critical state and if so, further reduce our concurrent streams.
      // For this reason we create a timer for 30 seconds that will call us back
      // with the last result in there were no change.
      const lastRecordArray = [records.at(records.length - 1)];
      timerId = setTimeout(pressureChange.bind(this, lastRecordArray), 30_000);
      for (const record of records) {
        if (record.state == "critical") {
          let streamsCount = getStreamsCount();
          setStreamsCount(streamsCount--);
        }
      }
    }
    const observer = new PressureObserver(pressureChange);
    observer.observe("cpu");
  
  
    In the following example, we want to demonstrate the usage of {{PressureObserver/takeRecords()}},
    by retrieving the remaining |records| accumulated since the the callback was last
    invoked.
  
  
    It is recommended to do so before {{PressureObserver/disconnect()}},
    otherwise {{PressureObserver/disconnect()}} will clear them and they will be lost forever.
  
  
    For example, we might want to measure the pressure during a benchmarking workload, and thus
    want pressure telemetry for the exact duration of the workload. This means disconnecting all
    observers immediately when the task is completed, and manually requesting any pending pressure
    telemetry up to this point that might not have been delivered yet as part of the event loop cycle.
  
  
    function logWorkloadStatistics(records) {
      // do something with records.
    }
    const observer = new PressureObserver(logWorkloadStatistics);
    observer.observe("cpu");
    // Read pending state change records, otherwise they will be cleared
    // when we disconnect.
    const records = observer.takeRecords();
    logWorkloadStatistics(records);
    observer.disconnect();
  
  
    In the following example, we show how to tell the observer to stop watching a specific
    |source:PressureSource| by invoking {{PressureObserver/unobserve()}}
    with |source|.
  
  
  
    const observer = new PressureObserver(records => { /* do something with records. */ });
    observer.observe("cpu");
    observer.observe("gpu");
    // Callback now gets called whenever the pressure state changes for 'cpu' or 'gpu'.
    observer.unobserve("gpu");
    // Callback now only gets called whenever the pressure state changes for 'cpu'.
  
  
    In the following example, we show how to tell the observer to stop watching for any
    state changes by calling {{PressureObserver/disconnect()}}. Calling
    {{PressureObserver/disconnect()}} will stop observing all sources observed
    by previous {{PressureObserver/observe()}} calls.
  
  
    Additionally it will clear all pending records collected since the last callback was invoked.
  
  
    const observer = new PressureObserver(records => { // do something with records. });
    observer.observe("cpu");
    observer.observe("gpu");
    // some time later...
    observer.disconnect();
    // records will be an empty array, because of the previous disconnect().
    const records = observer.takeRecords();