1. Introduction
performance.measureUserAgentSpecificMemory()
API that estimates the memory usage of the web application including all its iframes and workers.
The new API is intended for aggregating memory usage data from production. The main use cases are:
-
Regression detection during rollout of a new version of the web application to catch new memory leaks.
-
A/B testing a new feature to evaluate its memory impact.
-
Correlating memory usage with user metrics to understand the overall impact of memory usage.
1.1. Examples
A performance.measureUserAgentSpecificMemory()
call returns a Promise
and starts
an asynchronous measurement of the memory allocated by the page.
async function run() { const result= await performance. measureUserAgentSpecificMemory(); console. log( result); } run();
For a simple page without iframes and workers the result might look as follows:
Here all memory is attributed to the main page. The entry with{ bytes: 1000000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], }
bytes: 0
is present in the breakdown list to encourage processing of the result in a generic way without hardcoding specific entries.
Such an entry is inserted at a random position if the list is not empty.
Other possible valid results:
Here the implementation provides only the total memory usage.{ bytes: 1000000 , breakdown: [], }
Here the implementation does not break memory down by memory types.{ bytes: 1000000 , breakdown: [ { bytes: 0 , attribution: [], types: [], }, { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [], }, ], }
For a page that embeds a same-origin iframe the result might attribute some memory to that iframe and provide diagnostic information for identifying the iframe:
< html > < body > < iframe id = "example-id" src = "redirect.html?target=iframe.html" ></ iframe > </ body > </ html >
Note how the{ bytes: 1500000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "DOM" , "JS" ], }, { bytes: 0 , attribution: [], types: [], }, { bytes: 500000 , attribution: [ { url: "https://example.com/iframe.html" container: { id: "example-id" , src: "redirect.html?target=iframe.html" , }, scope: "Window" , } ], types: [ "JS" , "DOM" ], }, ], }
url
and container.src
fields differ for the iframe.
The former reflects the current location.href
of the iframe whereas the
latter is the value of the src
attribute of the iframe element.
It is not always possible to separate iframe memory from page memory in a meaningful way. An implementation is allowed to lump together some or all of iframe and page memory:
{ bytes: 1500000 , breakdown: [ { bytes: 1500000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, { url: "https://example.com/iframe.html" , container: { id: "example-id" , src: "redirect.html?target=iframe.html" , }, scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], };
An implementation might lump together worker and page memory. If a worker is spawned by an iframe, then the worker’s attribution entry has a{ bytes: 1800000 , breakdown: [ { bytes: 0 , attribution: [], types: [], }, { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 800000 , attribution: [ { url: "https://example.com/worker.js" , scope: "DedicatedWorkerGlobalScope" , }, ], types: [ "JS" ], }, ], };
container
field
corresponding to the iframe element.
Memory of shared and service workers is not included in the result.
Consider a page with the following structure:
exampleA cross-origin iframe embeds to other iframes and spawns a worker. All memory of these resources is attributed to the first iframe.. com( 1000000 bytes) | *-- foo. com/ iframe1( 500000 bytes) | *-- foo. com/ iframe2( 200000 bytes) | *-- bar. com/ iframe2( 300000 bytes) | *-- foo. com/ worker. js( 400000 bytes)
< html > < body > < iframe id = "example-id" src = "https://foo.com/iframe1" ></ iframe > </ body > </ html >
Note that the{ bytes: 2400000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, { bytes: 1400000 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "DOM" , "JS" ], }, ], }
url
and scope
fields of the cross-origin iframe entry have
special values indicating that information is not available.
If the implementation loads cross-origin iframes in a different address space,
then their memory usage is not measured.
The breakdown entries of such iframes have bytes: 0
indicating that these
iframes are not accounted for:
{ bytes: 100000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], }
location.href
of the same-origin iframe.
example. com( 1000000 bytes) | *-- foo. com/ iframe1( 500000 bytes) | *-- example. com/ iframe2( 200000 bytes)
< html > < body > < iframe id = "example-id" src = "https://foo.com/iframe1" ></ iframe > </ body > </ html >
{ bytes: 1700000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "DOM" , "JS" ], }, { bytes: 0 , attribution: [], types: [], }, { bytes: 500000 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "DOM" , "JS" ], }, { bytes: 200000 , attribution: [ { url: "https://example.com/iframe2" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, ], }
If the implementation omits memory measurement of cross-origin iframes, then the result could look like:
{ bytes: 1200000 , breakdown: [ { bytes: 1000000 , attribution: [ { url: "https://example.com" , scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [ { url: "cross-origin-url" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "cross-origin-aggregated" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 200000 , attribution: [ { url: "https://example.com/iframe2" , container: { id: "example-id" , src: "https://foo.com/iframe1" , }, scope: "Window" , }, ], types: [ "JS" , "DOM" ], }, { bytes: 0 , attribution: [], types: [], }, ], }
2. Data model
2.1. Memory measurement result
The performance.measureUserAgentSpecificMemory()
function returns a Promise
that resolves to an instance of MemoryMeasurement
dictionary:
dictionary {
MemoryMeasurement unsigned long long ;
bytes sequence <MemoryBreakdownEntry >; };
breakdown
measurement .
bytes
-
A number that represents the total memory usage.
measurement .
breakdown
-
An array that partitions the total
bytes
and provides attribution and type information.
dictionary {
MemoryBreakdownEntry unsigned long long ;
bytes sequence <MemoryAttribution >;
attribution sequence <DOMString >; };
types
breakdown .
bytes
-
The size of the memory that this entry describes.
breakdown .
attribution
-
An array of URLs and/or container elements of the JavaScript realms that use the memory.
breakdown .
types
-
An array of implementation-defined memory types associated with the memory.
dictionary {
MemoryAttribution USVString ;
url MemoryAttributionContainer ;
container DOMString ; };
scope
attribution .
url
-
If this attribution corresponds to a same-origin JavaScript realm, then this field contains realm’s URL. Otherwise, the attribution is for one or more cross-origin JavaScript realms and this field contains a sentinel value:
"cross-origin-url"
. attribution .
container
-
Describes the DOM element that (maybe indirectly) contains the JavaScript realms. This property might be absent if the attribution is for the same-origin top-level realm. Note that cross-origin realms cannot be top-level due to cross-origin isolation.
attribution .
scope
-
Describes the type of the same-origin JavaScript realm:
"Window", "DedicatedWorkerGlobalScope", "SharedWorkerGlobalScope", "ServiceWorkerGlobalScope"
or"cross-origin-aggregated"
for the cross-origin case.
dictionary {
MemoryAttributionContainer DOMString ;
id USVString ; };
src
2.2. Intermediate memory measurement
This specification assumes the existence of an implementation-defined algorithm that measures the memory usage of the given set of agent clusters in the address space of the given current agent cluster. The result of such an algorithm is an intermediate memory measurement, which is a set of intermediate memory breakdown entries.
To preserve security guarantees of address space isolation, any intermediate memory breakdown entries that represent memory outside the current address space must have their bytes set to 0.
To reduce fingerprinting risks the result must include only the memory related to the web platform objects allocated or used by the given set of agent clusters. This, for example, excludes the memory of user-agent-specific extensions and the baseline memory of an empty page. The memory must be accounted at the address space level to exclude any platform-specific memory optimizations such memory compression and lazy memory committing.
An intermediate memory breakdown entry is a struct containing the following items:
- bytes
-
The size of the memory that this intermediate memory breakdown entry describes, or 0 if this entry represents memory outside the current address space.
- realms
-
A set of JavaScript realms to which the memory is attributed to.
- types
-
A set of strings specifying implementation-defined memory types associated with the memory.
Algorithms defined in this specification show how to convert an intermediate memory measurement to an instance of MemoryMeasurement
.
2.3. Memory attribution token
The link between an embedded JavaScript realm and its container element is ephemeral and is not guaranteed to always exist. For example, navigation to another document in the container element or removal of the container element from the DOM tree severs the link.
A memory attribution token provides a way to get from a JavaScript realm to its container element. It is a struct containing the following items:
- container
-
An instance of
MemoryAttributionContainer
. - cross-origin aggregated flag
-
A boolean flag indicating whether the token was created for aggregating the memory usage of cross-origin JavaScript realms.
It is stored in a new internal field of WindowOrWorkerGlobalScope
at construction time and is always available for memory reporting.
3. Processing model
3.1. Extensions to the Performance
interface
partial interface Performance { [Exposed =(Window ,ServiceWorker ,SharedWorker ),CrossOriginIsolated ]Promise <MemoryMeasurement >measureUserAgentSpecificMemory (); };
performance .
measureUserAgentSpecificMemory()
-
A method that performs an asynchronous memory measurement. Details about the result of the method are in § 2.1 Memory measurement result.
3.2. Top-level algorithms
measureUserAgentSpecificMemory()
method steps are:
-
Assert: the current Realm's settings objects's cross-origin isolated capability is true.
-
If memory measurement allowed predicate given the current Realm is false, then:
-
Return a promise rejected with a "
SecurityError
"DOMException
.
-
-
Let the current agent cluster be the current Realm's agent's agent cluster.
-
Let agent clusters be the result of getting all agent clusters given the current Realm.
-
Let promise be a new
Promise
. -
Start asynchronous implementation-defined memory measurement given the current agent cluster, agent clusters, and promise.
-
Return promise.
-
Let global object be realm’s global object.
-
If global object is a
SharedWorkerGlobalScope
, then return true. -
If global object is a
ServiceWorkerGlobalScope
, then return true. -
If global object is a
Window
then-
Let settings object be realm’s settings object.
-
If settings object’s origin is the same as settings object’s top-level origin, then return true.
-
-
Return false.
-
If realm’s global object is a
Window
, then:-
Let group be the browsing context group that contains realm’s global object's browsing context.
-
Return the result of getting the values of group’s agent cluster map.
-
-
Return « realm’s agent's agent cluster ».
Promise
promise run these steps in parallel:
-
Let intermediate memory measurement be implementation-defined intermediate memory measurement performed for the current agent cluster and agent clusters.
-
Queue a global task on the TODO task source given promise’s relevant global object to resolve promise with the result of creating a new memory measurement given intermediate memory measurement.
3.3. Converting an intermediate memory measurement to the result
-
Let bytes be 0.
-
For each intermediate memory breakdown entry intermediate entry in intermediate measurement:
-
Set bytes to bytes plus intermediate entry’s bytes.
-
-
Let breakdown be a new list.
-
Append to breakdown a new
MemoryBreakdownEntry
whose:-
bytes
is 0, -
attribution
is « », -
types
is « ».
-
-
For each intermediate memory breakdown entry intermediate entry in intermediate measurement:
-
Let breakdown entry be the result of creating a new memory breakdown entry given intermediate entry.
-
Append breakdown entry to breakdown.
-
-
Randomize the order of the items in breakdown.
-
Return a new
MemoryMeasurement
whose:
-
Let attribution a new list.
-
For each JavaScript realm realm in intermediate entry’s realms:
-
Let attribution entry be the result of creating a new memory attribution given realm.
-
Append attribution entry to attribution.
-
-
Let types be intermediate entry’s types.
-
Randomize the order of the items in types.
-
Return a new
MemoryBreakdownEntry
whose:-
attribution
is attribution, -
types
is types.
-
Let token be realm’s global object's memory attribution token.
-
If token’s cross-origin aggregated flag is true, then
-
Let scope name be identifier of realm’s global object's interface.
-
Return a new
MemoryAttribution
whose:-
url
is realm’s settings object's creation URL, -
container
is token’s container if it is not null, otherwise, thecontainer
entry is omitted, -
scope
is scope name.
-
3.4. Creating or obtaining a memory attribution token
HTMLElement
container element, and a memory attribution token parent token:
-
If container element is null, then:
-
Assert: parent origin is null.
-
Assert: parent token is null.
-
Assert: origin is equal to parent origin
-
Return a new memory attribution token whose:
-
container is null,
-
cross-origin aggregated flag is false.
-
-
-
If parent origin is not equal to top-level origin, then:
-
Return parent token.
-
-
Let container be the result of extracting container element attributes given container element.
-
If origin is equal to top-level origin, then:
-
Return a new memory attribution token whose:
-
container is container,
-
cross-origin aggregated flag is false.
-
-
-
Return a new memory attribution token whose:
-
container is container,
-
cross-origin aggregated flag is true.
-
WorkerGlobalScope
worker global scope, an environment settings object outside settings:
-
If worker global scope is a
DedicatedWorkerGlobalScope
, then return outside settings’s global object's memory attribution token. -
Assert: worker global scope is a
SharedWorkerGlobalScope
or aServiceWorkerGlobalScope
. -
Return a new memory attribution token whose:
-
container is null,
-
cross-origin aggregated flag is false.
-
HTMLElement
container element:
-
Switch on container element’s local name:
- "iframe"
-
Return a new
MemoryAttributionContainer
whose: - "frame"
-
Return a new
MemoryAttributionContainer
whose: - "object"
-
Return a new
MemoryAttributionContainer
whose:
4. Integration with the existing specification
4.1. Extension to WindowOrWorkerGlobalScope
A new internal field is added to WindowOrWorkerGlobalScope
:
- A memory attribution token
-
An memory attribution token that is used for reporting the memory usage of this environment.
4.2. Extensions to the existing algorithms
The run a worker algorithm sets the memory attribution token field of the newly created global object in step 6:
Let realm execution context be the result of creating a new JavaScript realm given agent and the following customizations:
...
Set global object’s memory attribution token to the result of obtaining a worker memory attribution token given the global object and outside settings.
The create and initialize a Document object algorithm sets the memory attribution token field of the newly created global object:
Otherwise:
Let token be an empty memory attribution token.
If browsingContext is not a top-level browsing context, then:
Let parentToken be parentEnvironment’s global object's memory attribution token.
Set token to the result of obtaining a window memory attribution token with origin, parentEnvironment’s origin, topLevelOrigin, browsingContext’s container, parentToken.
Else, set token to the result of obtaining a window memory attribution token with origin, null topLevelOrigin, null, null.
Let window global scope be the global object of realm execution context’s Realm component.
Set window global scope’s memory attribution token to token.
The create a new browsing context algorithm sets the memory attribution token field of the newly created global object:
Let token be an empty token.
If embedder is null, then set token to the result of obtaining a window memory attribution token with origin, null, topLevelOrigin, null, null.
Else, set token to the result of obtaining a window memory attribution token with origin, embedder’s relevant settings object's origin, topLevelOrigin, embedder, embedder’s relevant global object's memory attribution token.
Let window global scope be the global object of realm execution context’s Realm component.
Set window global scope’s memory attribution token to token.
5. Privacy and Security
5.1. Cross-origin information leak
The URLs and other string values that appear in the result are guaranteed to be known to the origin that invokes the API.
The only information that is exposed cross-origin is the size information provided in memoryMeasurement.bytes
and memoryBreakdownEntry.bytes
.
The API relies on the cross-origin isolation mechanism to mitigate cross-origin size information leaks.
Specifically, the API relies on the invariant that all resources in the current address space have opted in to be embeddable and legible by their embedding origin.
The API does not expose the sizes of cross-origin resources loaded in a different address space.
5.2. Fingerprinting
The result of the API depends only on the objects allocated by the web page itself and does not include unrelated memory such as the baseline memory usage of an empty web page. This means the same user agent binary running on two different devices should produce the same results for a fixed web page.
A web page can infer the following information about the user agent:
-
the bitness of the user agent (32-bit vs 64-bit).
-
the version of the user agent to some extent.
Similar information can be obtained from the existing APIs (navigator.userAgent
, navigator.platform
).
The bitness of the user agent can also be inferred by measuring the runtime of 32-bit and 64-bit operations.
Currently the API is available only to the top-level origin. In the future the top-level origin will be able to delegate the API to other origins using Permissions Policy. In both cases, cross-origin iframes do not get access to the API by default.
6. Acknowledgements
Thanks to Domenic Denicola and Shu-yu Guo for contributing to the API design and for reviewing this specification.Also thanks to Adam Giacobbe, Anne van Kesteren, Artur Janc, Boris Zbarsky, Chris Hamilton, Chris Palmer, Daniel Vogelheim, Dominik Inführ, Hannes Payer, Joe Mason, Kentaro Hara, L. David Baron, Mathias Bynens, Matthew Bolohan, Michael Lippautz, Mike West, Neil Mckay, Olga Belomestnykh, Per Parker, Philipp Weis, and Yoav Weiss for their feedback and contributions.