Timing element visibility with the Intersection Observer API - Web APIs

Timing element visibility with the Intersection Observer API
============================================================

In this article, we'll build a mock blog which has a number of ads interspersed among the contents of the page, then use the Intersection Observer API to track how much time each ad is visible to the user. When an ad exceeds one minute of visible time, it will be replaced with a new one.

Although many aspects of this example will not match real-world usage (in particular, the articles all have the same text and aren't loaded from a database, and there are just a handful of simple text-only ads that are selected from an array), this should provide enough understanding of the API to quickly learn how to apply the Intersection Observer API to your own site.

There's a good reason why the notion of tracking visibility of ads is being used in this example. It turns out that one of the most common uses of Flash or other script in advertising on the Web is to record how long each ad is visible, for the purpose of billing and payment of revenues. Without the Intersection Observer API, this winds up being done using intervals and timeouts for each individual ad, or other techniques that tend to slow the page down. Using this API lets everything get streamlined by the browser to reduce the impact on performance substantially.

Let's get started!

Building the site
-----------------

### Site structure: The HTML

The site's structure is not too complicated. We'll be using CSS Grid to style and lay out the site, so we can be pretty straightforward here:

```html
<div class="wrapper">
  <header>
    <h1>A Fake Blog</h1>
    <h2>Showing Intersection Observer in action!</h2>
  </header>

  <aside>
    <nav>
      <ul>
        <li><a href="#link1">A link</a></li>
        <li><a href="#link2">Another link</a></li>
        <li><a href="#link3">One more link</a></li>
      </ul>
    </nav>
  </aside>

  <main>…</main>
</div>
```

This is the framework for the entire site. At the top is the site's header region, contained within a `<header>` block. Below that, we define the site's sidebar as a list of links within an `<aside>` block.

Finally comes the main body. We start with an empty `<main>` element here. This box will be populated using script later.

### Styling the site with CSS

With the structure of the site defined, we turn to the styling for the site. Let's look at the style for each component of the page individually.

#### The basics

We provide styles for the `<body>` and `<main>` elements to define the site's background as well as the grid the various parts of the site will be placed in.

```css
body {
  font-family: "Open Sans", "Arial", "Helvetica", sans-serif;
  background-color: aliceblue;
}

.wrapper {
  display: grid;
  grid-template-columns: auto minmax(min-content, 1fr);
  grid-template-rows: auto minmax(min-content, 1fr);
  max-width: 700px;
  margin: 0 auto;
  background-color: aliceblue;
}
```

The site's `<body>` is configured here to use one of a number of common sans-serif fonts, and to use `"aliceblue"` as the background color. Then the `"wrapper"` class is defined; it wraps the entire blog, including the header, sidebar, and body content (articles and ads).

The wrapper establishes a CSS grid with two columns and two rows. The first column (sized automatically based on its content) is used for the sidebar and the second column (which will be used for body content) is sized to be at least the width of the contents of the column and at most all remaining available space.

The first row will be used specially for the site header. The rows are sized the same way as the columns: the first one is automatically sized and the second one uses the remaining space, but at least enough space to provide room for all elements within it.

The wrapper's width is fixed at 700px so that it will fit in the available space when presented inline on MDN below.

#### The header

The header is fairly simple, since for this example all it contains is some text. Its style looks like this:

```css
header {
  grid-column: 1 / -1;
  grid-row: 1;
  background-color: aliceblue;
}
```

`grid-row` is set to 1, since we want the header to be placed in the top row of the site's grid. More interesting is our use of `grid-column` here; here we specify that we want the column to start in the first column and ends in the first column past the last grid line—in other words, the header spans across all of the columns within the grid. Perfect for our needs.

#### The sidebar

Our sidebar is used to present links to other pages on the site. None of them work in our example here, but they exist to help with the presentation of a blog-like experience. The sidebar is represented using an `<aside>` element, and is styled as follows:

```css
aside {
  grid-column: 1;
  grid-row: 2;
  background-color: cornsilk;
  padding: 5px 10px;
}

aside ul {
  padding-left: 0;
}

aside ul li {
  list-style: none;
}

aside ul li a {
  text-decoration: none;
}
```

The most important thing to note here is that the `grid-column` is set to 1, to place the sidebar on the left-hand side of the screen. If you change this to -1, it will appear on the right (although some other elements will need some adjustments made to their margins to get the spacing just right). The `grid-row` is set to 2, to place it alongside the site body.

#### The content body

Speaking of the site's body: the main content of the site is kept in a `<main>` element. The following style is applied to that:

```css
main {
  grid-column: 2;
  grid-row: 2;
  margin: 0;
  margin-left: 16px;
  font-size: 16px;
}
```

The primary feature here is that the grid position is set to place the body content in column 2, row 2.

#### Articles

Each article is contained in an `<article>` element, styled like this:

```css
article {
  background-color: white;
  padding: 6px;
}

article:not(:last-child) {
  margin-bottom: 8px;
}

article h2 {
  margin-top: 0;
}
```

This creates article boxes with a white background which float atop the blue background, with a small margin around the article. Every article which isn't the last item in the container has an 8px bottom margin to space things apart.

#### Ads

Finally, the ads have the following initial styling. Individual ads may customize the style somewhat, as we'll see later.

```css
.ad {
  height: 96px;
  padding: 6px;
  border-color: #555;
  border-style: solid;
  border-width: 1px;
}

.ad:not(:last-child) {
  margin-bottom: 8px;
}

.ad h2 {
  margin-top: 0;
}

.ad div {
  position: relative;
  float: right;
  padding: 0 4px;
  height: 20px;
  width: 120px;
  font-size: 14px;
  bottom: 30px;
  border: 1px solid black;
  background-color: rgb(255 255 255 / 50%);
}
```

There's nothing magic in here. It's fairly basic CSS.

### Tying it together with JavaScript

That brings us to the JavaScript code which makes everything work. Let's start with the global variables:

```js
let contentBox;

let nextArticleID = 1;
const visibleAds = new Set();
let previouslyVisibleAds = null;

let adObserver;
let refreshIntervalID = 0;
```

These are used as follows:

`contentBox`

A reference to the `<main>` element's `HTMLElement` object in the DOM. This is where we'll insert the articles and ads.

`nextArticleID`

Each article is given a unique ID number; this variable tracks the next ID to use, starting with 1.

`visibleAds`

A `Set` which we'll use to track the ads currently visible on the screen.

`previouslyVisibleAds`

Used to temporarily store the list of visible ads while the document is not visible (for example, if the user has tabbed to another page).

`adObserver`

Will hold our `IntersectionObserver` used to track the intersection between the ads and the `<main>` element's bounds.

`refreshIntervalID`

Used to store the interval ID returned by `setInterval()`. This interval will be used to trigger our periodic refreshes of the ads' content.

#### Setting up

To set things up, we run the `startup()` function below when the page loads:

```js
window.addEventListener("load", startup, false);

function startup() {
  contentBox = document.querySelector("main");

  document.addEventListener("visibilitychange", handleVisibilityChange, false);

  const observerOptions = {
    root: null,
    rootMargin: "0px",
    threshold: [0.0, 0.75],
  };

  adObserver = new IntersectionObserver(intersectionCallback, observerOptions);

  buildContents();
  refreshIntervalID = setInterval(handleRefreshInterval, 1000);
}
```

First, a reference to the content wrapping `<main>` element is obtained, so we can insert our content into it. Then we set up an event listener for the `visibilitychange` event. This event is sent when the document becomes hidden or visible, such as when the user switches tabs in their browser. The Intersection Observer API doesn't take this into account when detecting intersection, since intersection isn't affected by page visibility. Therefore, we need to pause our timers while the page is tabbed out; hence this event listener.

Next we set up the options for the `IntersectionObserver` which will monitor target elements (ads, in our case) for intersection changes relative to the document. The options are configured to watch for intersections with the document's viewport (by setting `root` to `null`). We have no margins to extend or contract the intersection root's rectangle; we want to match the boundaries of the document's viewport exactly for intersection purposes. And the `threshold` is set to an array containing the values 0.0 and 0.75; this will cause our callback to execute whenever a targeted element becomes completely obscured or first starts to become unobscured (intersection ratio 0.0) or passes through 75% visible in either direction (intersection ratio 0.75).

The observer, `adObserver`, is created by calling `IntersectionObserver`'s constructor, passing in the callback function, `intersectionCallback`, and our options.

We then call a function `buildContents()`, which we'll define later to actually generate and insert into the document the articles and ads we want to present.

Finally, we set up an interval which triggers once a second to handle any necessary refreshing. We need a one second refresh since we're displaying timers in all visible ads for the purposes of this example. You may not need an interval at all, or you might do it differently or using a different time interval.

#### Handling document visibility changes

Let's take a look at the handler for the `visibilitychange` event. Our script receives this event when the document itself becomes visible or invisible. The most important scenario here is when the user switches tabs. Since Intersection Observer only cares about the intersection between the targeted elements and the intersection root, and not the tab's visibility (which is a different issue entirely), we need to use the Page Visibility API to detect these tab switches and disable our timers for the duration.

```js
function handleVisibilityChange() {
  if (document.hidden) {
    if (!previouslyVisibleAds) {
      previouslyVisibleAds = visibleAds;
      visibleAds = [];
      previouslyVisibleAds.forEach((adBox) => {
        updateAdTimer(adBox);
        adBox.dataset.lastViewStarted = 0;
      });
    }
  } else {
    previouslyVisibleAds.forEach((adBox) => {
      adBox.dataset.lastViewStarted = performance.now();
    });
    visibleAds = previouslyVisibleAds;
    previouslyVisibleAds = null;
  }
}
```

Since the event itself doesn't state whether the document has switched from visible to invisible or vice versa, the `document.hidden` property is checked to see if the document is not currently visible. Since it's theoretically possible to get called multiple times, we only proceed if we haven't already paused the timers and saved the visibility states of the existing ads.

To pause the timers, all we need to do is remove the ads from the set of visible ads (`visibleAds`) and mark them as inactive. To do so, we begin by saving the set of visible ads into a variable known as `previouslyVisibleAds` to be sure we can restore them when the user tabs back into the document, and we then empty the `visibleAds` set so they won't be treated as visible. Then, for each of the ads that are being suspended, we call our `updateAdTimer()` function, which handles updating the ad's total visible time counter, then we set their `dataset.lastViewStarted` property to 0, which indicates that the tab's timer isn't running.

If the document has just become visible, we reverse this process: first we go through `previouslyVisibleAds` and set each one's `dataset.lastViewStarted` to the current document's time (in milliseconds since the document was created) using the `performance.now()` method. Then we set `visibleAds` back to `previouslyVisibleAds` and set the latter to `null`. Now the ads are all restarted, and configured to know that they became visible at the current time, so that they will not add up the duration of time the page was tabbed away the next time they're updated.

#### Handling intersection changes

Once per pass through the browser's event loop, each `IntersectionObserver` checks to see if any of its target elements have passed through any of the observer's intersection ratio thresholds. For each observer, a list of targets that have done so is compiled, and sent to the observer's callback as an array of `IntersectionObserverEntry` objects. Our callback, `intersectionCallback()`, looks like this:

```js
function intersectionCallback(entries) {
  entries.forEach((entry) => {
    const adBox = entry.target;

    if (entry.isIntersecting) {
      if (entry.intersectionRatio >= 0.75) {
        adBox.dataset.lastViewStarted = entry.time;
        visibleAds.add(adBox);
      }
    } else {
      visibleAds.delete(adBox);
      if (
        entry.intersectionRatio === 0.0 &&
        adBox.dataset.totalViewTime >= 60000
      ) {
        replaceAd(adBox);
      }
    }
  });
}
```

As previously mentioned, the `IntersectionObserver` callback receives as input an array of all of the observer's targeted elements which have become either more or less visible than one of the intersection observer ratios. We iterate over each of those entries—which are of type `IntersectionObserverEntry`. If the target element is intersecting with the root, we know it has just transitioned from the obscured state to the visible state. If it's become at least 75% visible, then we consider the ad visible and we start the timer by setting the ad's `dataset.lastViewStarted` attribute to the transition time in `entry.time`, then add the ad to the set `visibleAds` so we know to process it as time goes by.

If the ad has transitioned to the not-intersecting state, we remove the ad from the set of visible ads. Then we have one special behavior: we look to see if `entry.ratio` is 0.0; if it is, that means the element has become totally obscured. If that's the case, and the ad has been visible for at least a minute total, we call a function we'll create called `replaceAd()` to replace the existing ad with a new one. This way, the user sees a variety of ads over time, but the ads are only replaced while they can't be seen, resulting in a smooth experience.

#### Handling periodic actions

Our interval handler, `handleRefreshInterval()`, is called about once per second courtesy of the call to `setInterval()` made in the `startup()` function described above. Its main job is to update the timers every second and schedule a redraw to update the timers we'll be drawing within each ad.

```js
function handleRefreshInterval() {
  const redrawList = [];

  visibleAds.forEach((adBox) => {
    const previousTime = adBox.dataset.totalViewTime;
    updateAdTimer(adBox);

    if (previousTime !== adBox.dataset.totalViewTime) {
      redrawList.push(adBox);
    }
  });

  if (redrawList.length) {
    window.requestAnimationFrame((time) => {
      redrawList.forEach((adBox) => {
        drawAdTimer(adBox);
      });
    });
  }
}
```

The array `redrawList` will be used to keep a list of all the ads which need to be redrawn during this refresh cycle, since it may not be exactly the same as the elapsed time due to system activity or because you've set the interval to something other than every 1000 milliseconds.

Then, for each of the visible ads, we save the value of `dataset.totalViewTime` (the total number of milliseconds the ad has currently been visible, as of the last time it was updated) and then call `updateAdTimer()` to update the time. If it's changed, then we push the ad onto the `redrawList` so we know it needs to be updated during the next animation frame.

Finally, if there's at least one element to redraw, we use `requestAnimationFrame()` to schedule a function that will redraw each element in the `redrawList` during the next animation frame.

#### Updating an ad's visibility timer

Previously (see Handling document visibility changes and Handling periodic actions), we've seen that when we need to update an ad's "total visible time" counter, we call a function named `updateAdTimer()` to do so. This function takes as an input an ad's `HTMLDivElement` object. Here it is:

```js
function updateAdTimer(adBox) {
  const lastStarted = adBox.dataset.lastViewStarted;
  const currentTime = performance.now();

  if (lastStarted) {
    const diff = currentTime - lastStarted;

    adBox.dataset.totalViewTime =
      parseFloat(adBox.dataset.totalViewTime) + diff;
  }

  adBox.dataset.lastViewStarted = currentTime;
}
```

To track an element's visible time, we use two custom data attributes (see `data-*`) on every ad:

`lastViewStarted`

The time in milliseconds, relative to the time at which the document was created, at which the ad's visibility count was last updated, or the ad last became visible. 0 if the ad was not visible as of the last time it was checked.

`totalViewTime`

The total number of milliseconds the ad has been visible.

These are accessed through each ad's `HTMLElement.dataset` attribute, which provides a `DOMStringMap` mapping each custom attribute's name to its value. The values are strings, but we can convert those to numbers easily enough—in fact, JavaScript generally does it automatically, although we'll have one instance where we have to do it ourselves.

We start by fetching the time at which the ad's previous visibility status check time (`adBox.dataset.lastViewStarted`) into a local variable named `lastStarted`. We also get the current time-since-creation value using `performance.now()` into `currentTime`.

If `lastStarted` is non-zero—meaning the timer is currently running, we compute the difference between the current time and the start time to determine the number of milliseconds the timer has been visible since the last time it became visible. This is added to the current value of the ad's `totalViewTime` to bring the total up to date. Note the use of `parseFloat()` here; because these values are strings, JavaScript tries to do a string concatenation instead of addition without it.

Finally, the last-viewed time for the ad is updated to the current time. This is done whether the ad was running when this function was called or not; this causes the ad's timer to always be running when this function returns. This makes sense because this function is only called if the ad is visible, even if it's just now become visible.

#### Drawing an ad's timer

Inside each ad, for demonstration purposes, we draw the current value of its `totalViewTime`, converted into minutes and seconds. This is handled by passing the ad's element into the `drawAdTimer()` function:

```js
function drawAdTimer(adBox) {
  const timerBox = adBox.querySelector(".timer");
  const totalSeconds = adBox.dataset.totalViewTime / 1000;
  const sec = Math.floor(totalSeconds % 60);
  const min = Math.floor(totalSeconds / 60);

  timerBox.innerText = `${min}:${sec.toString().padStart(2, "0")}`;
}
```

This code finds the ad's timer using its ID, `"timer"`, and computes the number of seconds elapsed by dividing the ad's `totalViewTime` by 1000. Then it calculates the number of minutes and seconds elapsed before setting the timer's `innerText` to a string representing that time in the form m:ss. The `String.padStart()` method is used to ensure that the number of seconds is padded out to two digits if it's less than 10.

#### Building the page contents

The `buildContents()` function is called by the startup code to select and insert into the document the articles and ads to be presented:

```js
const loremIpsum =
  "<p>Lorem ipsum dolor sit amet, consectetur adipiscing" +
  " elit. Cras at sem diam. Vestibulum venenatis massa in tincidunt" +
  " egestas. Morbi eu lorem vel est sodales auctor hendrerit placerat" +
  " risus. Etiam rutrum faucibus sem, vitae mattis ipsum ullamcorper" +
  " eu. Donec nec imperdiet nibh, nec vehicula libero. Phasellus vel" +
  " malesuada nulla. Aliquam sed magna aliquam, vestibulum nisi at," +
  " cursus nunc.</p>";

function buildContents() {
  for (let i = 0; i < 5; i++) {
    contentBox.appendChild(createArticle(loremIpsum));

    if (!(i % 2)) {
      loadRandomAd();
    }
  }
}
```

The variable `loremIpsum` contains the text we'll use for the body of all of our articles. Obviously in the real world, you'd have some code to pull articles from a database or the like, but this does the job for our purposes. Every article uses the same text; you could of course change that easily enough.

`buildContents()` creates a page with five articles. Following every odd-numbered article, an ad is "loaded" and inserted into the page. Articles are inserted into the content box (that is, the `<main>` element that contains all the site content) after being created using a method called `createArticle()`, which we'll look at next.

The ads are created using a function called `loadRandomAd()`, which both creates the ad and inserts it into the page. We'll see later that this same function can also replace an existing ad, but for now, we're appending ads to the existing content.

#### Creating an article

To create the `<article>` element for an article (as well as all of its contents), we use the `createArticle()` function, which takes as input a string which is the full text of the article to add to the page.

```js
function createArticle(contents) {
  const articleElem = document.createElement("article");
  articleElem.id = nextArticleID;

  const titleElem = document.createElement("h2");
  titleElem.innerText = `Article ${nextArticleID} title`;
  articleElem.appendChild(titleElem);

  articleElem.innerHTML += contents;
  nextArticleID += 1;

  return articleElem;
}
```

First, the `<article>` element is created and its ID is set to the unique value `nextArticleID` (which starts at 1 and goes up for each article). Then we create and append an h2 element for the article title and then we append the HTML from `contents` to that. Finally, `nextArticleID` is incremented (so that the next element gets a new unique ID) and we return the new `<article>` element to the caller.

#### Creating an ad

The `loadRandomAd()` function simulates loading an ad and adding it to the page. If you don't pass a value for `replaceBox`, a new element is created to contain the ad; the ad is then appended to the page. If you specify a `replaceBox`, that box is treated as an existing ad element; instead of creating a new one, the existing element is changed to contain the new ad's style, content, and other data. This avoids the risk of lengthy layout work being done when you update the ad, which could happen if you first delete the old element then insert a new one.

```js
function loadRandomAd(replaceBox) {
  const ads = [
    {
      bgcolor: "#cec",
      title: "Eat Green Beans",
      body: "Make your mother proud—they're good for you!",
    },
    {
      bgcolor: "aquamarine",
      title: "MillionsOfFreeBooks.whatever",
      body: "Read classic literature online free!",
    },
    {
      bgcolor: "lightgrey",
      title: "3.14 Shades of Gray: A novel",
      body: "Love really does make the world go round…",
    },
    {
      bgcolor: "#fee",
      title: "Flexbox Florist",
      body: "When life's layout gets complicated, send flowers.",
    },
  ];
  let adBox, title, body, timerElem;

  const ad = ads[Math.floor(Math.random() \* ads.length)];

  if (replaceBox) {
    adObserver.unobserve(replaceBox);
    adBox = replaceBox;
    title = replaceBox.querySelector(".title");
    body = replaceBox.querySelector(".body");
    timerElem = replaceBox.querySelector(".timer");
  } else {
    adBox = document.createElement("div");
    adBox.className = "ad";
    title = document.createElement("h2");
    body = document.createElement("p");
    timerElem = document.createElement("div");
    adBox.appendChild(title);
    adBox.appendChild(body);
    adBox.appendChild(timerElem);
  }

  adBox.style.backgroundColor = ad.bgcolor;

  title.className = "title";
  body.className = "body";
  title.innerText = ad.title;
  body.innerHTML = ad.body;

  adBox.dataset.totalViewTime = 0;
  adBox.dataset.lastViewStarted = 0;

  timerElem.className = "timer";
  timerElem.innerText = "0:00";

  if (!replaceBox) {
    contentBox.appendChild(adBox);
  }

  adObserver.observe(adBox);
}
```

First is the array `ads`. This array contains the data needed to create each ad. We have four here to choose from at random. In a real-world scenario, of course, the ads would come from a database or, more likely, an advertising service from which you fetch ads using an API. However, our needs are simple: each ad is represented by an object with three properties: a background color (`bgcolor`), a title (`title`), and a body text string (`body`).

Then we define several variables:

`adBox`

This will be set to the element that represents the ad. For new ads being appended to the page, this is created using `Document.createElement()`. When replacing an existing ad, this is set to the specified ad element (`replaceBox`).

`title`

Will hold the h2 element representing the ad's title.

`body`

Will hold the `<p>` representing the ad's body text.

`timerElem`

Will hold the `<div>` element which contains the time the ad has been visible so far.

A random ad is selected by computing `Math.floor(Math.random() * ads.length)`; the result is a value between 0 and one less than the number of ads. The corresponding ad is now known as `adBox`.

If a value is specified for `replaceBox`, we use that as the ad element. To do so, we begin by ending observation of the element by calling `IntersectionObserver.unobserve()`. Then the local variables for each of the elements that comprise an ad: the ad box itself, the title, the body, and the timer box, are all set to the corresponding elements in the existing ad.

If no value is specified for replaceBox, we create a new ad element. The ad's new `<div>` element is created and its properties established by setting its class name to `"ad"`. Next, the ad title element is created, along with the body and the visibility timer; these are an h2, a `<p>`, and a `<div>` element, respectively. These elements are appended to the `adBox` element.

After that, the code paths converge once again. The ad's background color is set to the value specified in the new ad's record, and elements' classes and contents are set appropriately as well.

Next, it's time to set up the custom data properties to track the ad's visibility data by setting `adBox.dataset.totalViewTime` and `adBox.dataset.lastViewStarted` to 0.

Finally, we set the ID of the `<div>` which will show the timer we'll present in the ad to show how long it's been visible, giving it the class `"timer"`. The initial text is set to "0:00", to represent the starting time of 0 minutes and 0 seconds, and it's appended to the ad.

If we're not replacing an existing ad, we need to append the element to the content area of the page using `Document.appendChild()`. If we're replacing an ad, it's already there, with its contents replaced with the new ad's. Then we call the `observe()` method on our Intersection Observer, `adObserver`, to start watching the ad for changes to its intersection with the viewport. From now on, any time the ad becomes 100% obscured or even a single pixel becomes visible, or the ad passes through 75% visible in one way or another, the observer's callback is executed.

#### Replacing an existing ad

Our observer's callback keeps an eye out for ads which become 100% obscured and have a total visible time of at least one minute. When that happens, the `replaceAd()` function is called with that ad's element as an input, so that the old ad can be replaced with a new one.

```js
function replaceAd(adBox) {
  updateAdTimer(adBox);

  const visibleTime = adBox.dataset.totalViewTime;
  console.log(
    `Replacing ad: ${
 adBox.querySelector("h2").innerText
 } - visible for ${visibleTime}`,
  );

  loadRandomAd(adBox);
}
```

`replaceAd()` begins by calling `updateAdTimer()` on the existing ad, to ensure that its timer is up-to-date. This ensures that when we read its `totalViewTime`, we see the exact final value for how long the ad was visible to the user. We then report that data; in this case, by logging it to console, but in the real world, you'd submit the information to an ad service's API or save it into a database.

Then we load a new ad by calling `loadRandomAd()`, specifying the ad to be replaced as an input parameter. As we saw previously, `loadRandomAd()` will replace an existing ad with content and data corresponding to a new ad, if you specify an existing ad's element as an input parameter.

The new ad's element object is returned to the caller in case it's needed.

### Result

The resulting page looks like this. Try experimenting by scrolling up and down and notice how changes in the visibility affect the timers in each ad. Also note that each ad is replaced after one minute of visibility (but the ad must first be scrolled out of view and back again), and how the timers pause while the document is tabbed into the background. However, covering the browser with another window does not pause the timers.

See also
--------

* Intersection Observer API
* Page Visibility API


URLSearchParams: delete() method - Web APIs

URLSearchParams: delete() method
================================

The **`delete()`** method of the `URLSearchParams` interface deletes specified parameters and their associated value(s) from the list of all search parameters.

 A parameter name and optional value are used to match parameters.
 If only a parameter name is specified, then all search parameters that match the name are deleted, along with their associated values.
 If both a parameter name and value are specified, then all search parameters that match both the parameter name and value are deleted.

**Note:** This feature is available in Web Workers

Syntax
------

```js
delete(name)
delete(name, value)
```

### Parameters

`name`

The name of the parameters to be deleted.

`value` Optional

The value that parameters must match, along with the given name, to be deleted.

### Return value

None (`undefined`).

Examples
--------

### Delete all parameters with specified name

This example shows how to delete all query parameters (and values) that have a particular name.
```

<pre id="log"></pre>
```

```
const logElement = document.getElementById("log");
function log(text) {
  logElement.innerText += `${text}\n`;
}
```

```js
const url = new URL("https://example.com?foo=1&bar=2&foo=3");
const params = new URLSearchParams(url.search);
log(`Query string (before):\t ${params}`);
params.delete("foo");
log(`Query string (after):\t ${params}`);
```

The log below shows that all parameters that have the name of `foo` are deleted.

### Delete parameters with specified name and value

This example shows how to delete query parameters that match a particular name and value.
```

<pre id="log"></pre>
```

```
const logElement = document.getElementById("log");
function log(text) {
  logElement.innerText += `${text}\n`;
}
```

```js
const url = new URL("https://example.com?foo=1&bar=2&foo=3&foo=1");
const params = new URLSearchParams(url.search);
log(`Query string (before):\t ${params}`);
params.delete("foo", "1");
log(`Query string (after):\t ${params}`);
```

All parameters that match both the parameter `name` and `value` should be deleted (there is no reason to specify two parameters with the same name and value as shown above).

 If your browser supports the `value` option, the "after" string should be `bar=2&foo=3`.
 Otherwise the result will be the same as in the previous example (`bar=2`).

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-delete |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* Polyfill of `URLSearchParams` in `core-js`


URLSearchParams: forEach() method - Web APIs

URLSearchParams: forEach() method
=================================

 The **`forEach()`** method of the
 `URLSearchParams` interface allows iteration through all values contained
 in this object via a callback function.

**Note:** This feature is available in Web Workers

Syntax
------

```js
forEach(callback)
forEach(callback, thisArg)
```

### Parameters

`callback`

Function to execute on each element, which is passed the following arguments:

`value`

The value of the current entry being processed in the `URLSearchParams` object.

`key`

The key of the current entry being processed in the `URLSearchParams` object.

`searchParams`

The `URLSearchParams` object the `forEach()` was called upon.

`thisArg` Optional

Value to use as `this` when executing `callback`.

### Return value

None (`undefined`).

Examples
--------

```js
// Create a test URLSearchParams object
const searchParams = new URLSearchParams("key1=value1&key2=value2");

// Log the values
searchParams.forEach((value, key) => {
  console.log(value, key);
});
```

The result is:
```

value1 key1
value2 key2
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-urlsearchparams |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The `URL` interface.


URLSearchParams: toString() method - Web APIs

URLSearchParams: toString() method
==================================

 The **`toString()`** method of the
 `URLSearchParams` interface returns a query string suitable for use in a
 URL.

**Note:** This method returns the query string without the question mark. This is different from `Location.search`, `HTMLAnchorElement.search`, and `URL.search`, which all include the question mark.

**Note:** This feature is available in Web Workers

Syntax
------

```js
toString()
```

### Parameters

None.

### Return value

 A string, without the question mark. (Returns an empty string if no
 search parameters have been set.)

Examples
--------

```js
const url = new URL("https://example.com?foo=1&bar=2");
const params = new URLSearchParams(url.search);

// Add a second foo parameter.
params.append("foo", 4);
console.log(params.toString()); // Prints 'foo=1&bar=2&foo=4'
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # urlsearchparams-stringification-behavior |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The `URL` interface.
* Google Developers: Easy URL manipulation with URLSearchParams


URLSearchParams: get() method - Web APIs

URLSearchParams: get() method
=============================

 The **`get()`** method of the `URLSearchParams`
 interface returns the first value associated to the given search parameter.

**Note:** This feature is available in Web Workers

Syntax
------

```js
get(name)
```

### Parameters

`name`

The name of the parameter to return.

### Return value

 A string if the given search parameter is found; otherwise,
 **`null`**.

Examples
--------

 If the URL of your page is `https://example.com/?name=Jonathan&age=18`
 you could parse out the 'name' and 'age' parameters using:

```js
let params = new URLSearchParams(document.location.search);
let name = params.get("name"); // is the string "Jonathan"
let age = parseInt(params.get("age"), 10); // is the number 18
```

 Requesting a parameter that isn't present in the query string will return
 **`null`**:

```js
let address = params.get("address"); // null
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-get |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


URLSearchParams: has() method - Web APIs

URLSearchParams: has() method
=============================

The **`has()`** method of the `URLSearchParams` interface returns a boolean value that indicates whether the specified parameter is in the search parameters.

 A parameter name and optional value are used to match parameters.
 If only a parameter name is specified, then the method will return `true` if any parameters in the query string match the name, and `false` otherwise.
 If both a parameter name and value are specified, then the method will return `true` if a parameter matches both the name and value.

**Note:** This feature is available in Web Workers

Syntax
------

```js
has(name)
has(name, value)
```

### Parameters

`name`

The name of the parameter to match.

`value`

The value of the parameter, along with the given name, to match.

### Return value

A boolean value.

Examples
--------

### Check for parameter with specified name

This example shows how to check if the query string has any parameters with a particular name.
```

<pre id="log"></pre>
```

```
const logElement = document.getElementById("log");
function log(text) {
  logElement.innerText += `${text}\n`;
}
```

```js
const url = new URL("https://example.com?foo=1&bar=2&foo=3");
const params = new URLSearchParams(url.search);

// has() returns true if the parameter is in the query string
log(`bar?:\t${params.has("bar")}`);
log(`bark?:\t${params.has("bark")}`);
log(`foo?:\t${params.has("foo")}`);
```

The log below shows whether the parameters `bar`, `bark`, and `foo`, are present in the query string.

### Check for parameter with specified name and value

This example shows how to check whether the query string has a parameter that matches both a particular name and value.
```

<pre id="log"></pre>
```

```
const logElement = document.getElementById("log");
function log(text) {
  logElement.innerText += `${text}\n`;
}
```

```js
const url = new URL("https://example.com?foo=1&bar=2&foo=3");
const params = new URLSearchParams(url.search);

// has() returns true if a parameter with the matching name and value is in the query string
log(`bar=1?:\t${params.has("bar", "1")}`);
log(`bar=2?:\t${params.has("bar", "2")}`);
log(`foo=4?:\t${params.has("foo", "4")}`);
```

Only the second value above should be `true`, as only the parameter name `bar` with value `2` is matched.

If your browser does not support the `value` option the method will match on the name, and all the results should be `true`.

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-has |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* Polyfill of `URLSearchParams` in `core-js`


URLSearchParams: URLSearchParams() constructor - Web APIs

URLSearchParams: URLSearchParams() constructor
==============================================

 The **`URLSearchParams()`** constructor creates and returns a
 new `URLSearchParams` object.

**Note:** This feature is available in Web Workers

Syntax
------

```js
new URLSearchParams()
new URLSearchParams(options)
```

### Parameters

`options` Optional

One of:

* A string, which will be parsed from `application/x-www-form-urlencoded` format. A leading `'?'` character is ignored.
* A literal sequence of name-value string pairs, or any object — such as a `FormData` object — with an iterator that produces a sequence of string pairs. Note that `File` entries will be serialized as `[object File]` rather than as their filename (as they would in an `application/x-www-form-urlencoded` form).
* A record of string keys and string values. Note that nesting is not supported.

### Return value

A `URLSearchParams` object instance.

Examples
--------

 The following example shows how to create a `URLSearchParams` object from
 various inputs.

```js
// Retrieve params via url.search, passed into constructor
const url = new URL("https://example.com?foo=1&bar=2");
const params1 = new URLSearchParams(url.search);

// Get the URLSearchParams object directly from a URL object
const params1a = url.searchParams;

// Pass in a string literal
const params2 = new URLSearchParams("foo=1&bar=2");
const params2a = new URLSearchParams("?foo=1&bar=2");

// Pass in a sequence of pairs
const params3 = new URLSearchParams([
  ["foo", "1"],
  ["bar", "2"],
]);

// Pass in a record
const params4 = new URLSearchParams({ foo: "1", bar: "2" });
```

This example shows how to build a new URL with an object of search parameters from an existing URL that has search parameters.

```js
const url = new URL("https://example.com/?a=hello&b=world");

console.log(url.href);
// https://example.com/?a=hello&b=world

console.log(url.origin);
// https://example.com

const add_params = {
  c: "a",
  d: new String(2),
  e: false.toString(),
};

const new_params = new URLSearchParams([
  ...Array.from(url.searchParams.entries()), // [["a","hello"],["b","world"]]
  ...Object.entries(add_params), // [["c","a"],["d","2"],["e","false"]]
]).toString();
console.log(new_params);
// a=hello&b=world&c=a&d=2&e=false

const new_url = new URL(`${url.origin}${url.pathname}?${new\_params}`);

console.log(new_url.href);
// https://example.com/?a=hello&b=world&c=a&d=2&e=false

// Here it is as a function that accepts (URL, Record<string, string>)
const addSearchParams = (url, params = {}) =>
  new URL(
    `${url.origin}${url.pathname}?${new URLSearchParams([
 ...Array.from(url.searchParams.entries()),
 ...Object.entries(params),
 ])}`,
  );
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-urlsearchparams |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


URLSearchParams: sort() method - Web APIs

URLSearchParams: sort() method
==============================

 The **`URLSearchParams.sort()`** method sorts all key/value
 pairs contained in this object in place and returns `undefined`. The sort
 order is according to unicode code points of the keys. This method uses a stable sorting
 algorithm (i.e. the relative order between key/value pairs with equal keys will be
 preserved).

**Note:** This feature is available in Web Workers

Syntax
------

```js
sort()
```

### Parameters

None.

### Return value

None (`undefined`).

Examples
--------

```js
// Create a test URLSearchParams object
const searchParams = new URLSearchParams("c=4&a=2&b=3&a=1");

// Sort the key/value pairs
searchParams.sort();

// Display the sorted query string
console.log(searchParams.toString());
```

The result is:
```

a=2&a=1&b=3&c=4
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-sort |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


URLSearchParams: keys() method - Web APIs

URLSearchParams: keys() method
==============================

 The **`keys()`** method of the `URLSearchParams`
 interface returns an `iterator` allowing iteration
 through all keys contained in this object. The keys are string
 objects.

**Note:** This feature is available in Web Workers

Syntax
------

```js
keys()
```

### Parameters

None.

### Return value

Returns an `iterator`.

Examples
--------

```js
// Create a test URLSearchParams object
const searchParams = new URLSearchParams("key1=value1&key2=value2");

// Display the keys
for (const key of searchParams.keys()) {
  console.log(key);
}
```

The result is:
```

key1
key2
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-urlsearchparams |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The `URL` interface.


URLSearchParams: set() method - Web APIs

URLSearchParams: set() method
=============================

 The **`set()`** method of the `URLSearchParams`
 interface sets the value associated with a given search parameter to the given value.
 If there were several matching values, this method deletes the others. If the search
 parameter doesn't exist, this method creates it.

**Note:** This feature is available in Web Workers

Syntax
------

```js
set(name, value)
```

### Parameters

`name`

The name of the parameter to set.

`value`

The value of the parameter to set.

### Return value

None (`undefined`).

Examples
--------

```js
let url = new URL("https://example.com?foo=1&bar=2");
let params = new URLSearchParams(url.search);

// Add a third parameter.
params.set("baz", 3);
params.toString(); // "foo=1&bar=2&baz=3"
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-set |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


URLSearchParams: size property - Web APIs

URLSearchParams: size property
==============================

The **`size`** read-only property of the `URLSearchParams` interface indicates the total number of search parameter entries.

**Note:** This feature is available in Web Workers

Value
-----

A number indicating the total number of search parameter entries in the `URLSearchParams` object.

Examples
--------

### Getting the amount of search parameter entries

You can get the total number of search parameter entries like so:

```js
const searchParams = new URLSearchParams("c=4&a=2&b=3&a=1");
searchParams.size; // 4
```

Note how the `a` parameter is given twice, but `size` returns the number of all given entries (4) and not 3. To get the amount of unique keys, you can use a `Set`, for example:

```js
[...new Set(searchParams.keys())].length; // 3
```

### Checking if search parameters exist

The `size` property is useful for checking whether there are any search parameters at all:

```js
const url = new URL("https://example.com?foo=1&bar=2");

if (url.searchParams.size) {
  console.log("URL has search parameters!");
}
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-size |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `URL.searchParams`
* Polyfill of `URLSearchParams` in `core-js`


URLSearchParams: values() method - Web APIs

URLSearchParams: values() method
================================

 The **`values()`** method of the `URLsearchParams`
 interface returns an `iterator` allowing iteration
 through all values contained in this object. The values are string
 objects.

**Note:** This feature is available in Web Workers

Syntax
------

```js
values()
```

### Parameters

None.

### Return value

Returns an `iterator`.

Examples
--------

The following example passes a URL search string to the `URLSearchParams` constructor, then uses the iterator returned by `values()` to print the values to the console.

```js
const searchParams = new URLSearchParams("key1=value1&key2=value2");

for (const value of searchParams.values()) {
  console.log(value);
}
```

The result is:
```

value1
value2
```

This example does much the same as above, but first casts the iterator into an array.

```js
const searchParams = new URLSearchParams("key1=value1&key2=value2");

console.log(Array.from(searchParams.values()));
```

The result is:
```

['value1', 'value2']
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-urlsearchparams |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The `URL` interface.


URLSearchParams: getAll() method - Web APIs

URLSearchParams: getAll() method
================================

 The **`getAll()`** method of the `URLSearchParams`
 interface returns all the values associated with a given search parameter as an array.

**Note:** This feature is available in Web Workers

Syntax
------

```js
getAll(name)
```

### Parameters

`name`

The name of the parameter to return.

### Return value

An array of strings, which may be empty if no values for the given parameter are found.

Examples
--------

```js
let url = new URL("https://example.com?foo=1&bar=2");
let params = new URLSearchParams(url.search);

//Add a second foo parameter.
params.append("foo", 4);

console.log(params.getAll("foo")); //Prints ["1","4"].
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-getall |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


URLSearchParams: append() method - Web APIs

URLSearchParams: append() method
================================

 The **`append()`** method of the `URLSearchParams`
 interface appends a specified key/value pair as a new search parameter.

 As shown in the example below, if the same key is appended multiple times it will
 appear in the parameter string multiple times for each value.

**Note:** This feature is available in Web Workers

Syntax
------

```js
append(name, value)
```

### Parameters

`name`

The name of the parameter to append.

`value`

The value of the parameter to append.

### Return value

None (`undefined`).

Examples
--------

```js
let url = new URL("https://example.com?foo=1&bar=2");
let params = new URLSearchParams(url.search);

//Add a second foo parameter.
params.append("foo", 4);
//Query string is now: 'foo=1&bar=2&foo=4'
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-append |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `URL`
* Google Developers: Easy URL manipulation with URLSearchParams


URLSearchParams: entries() method - Web APIs

URLSearchParams: entries() method
=================================

 The **`entries()`** method of the
 `URLSearchParams` interface returns an
 `iterator` allowing iteration through all key/value
 pairs contained in this object. The iterator returns key/value pairs in the same order as they appear in the query string. The key and value of each pair are
 string objects.

**Note:** This feature is available in Web Workers

Syntax
------

```js
entries()
```

### Parameters

None.

### Return value

Returns an `iterator`.

Examples
--------

```js
// Create a test URLSearchParams object
const searchParams = new URLSearchParams("key1=value1&key2=value2");

// Display the key/value pairs
for (const [key, value] of searchParams.entries()) {
  console.log(`${key}, ${value}`);
}
```

The result is:
```

key1, value1
key2, value2
```

Specifications
--------------

| Specification |
| --- |
| URL Standard # dom-urlsearchparams-urlsearchparams |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The `URL` interface.


GPUComputePassEncoder: insertDebugMarker() method - Web APIs

GPUComputePassEncoder: insertDebugMarker() method
=================================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`insertDebugMarker()`** method of the
 `GPUComputePassEncoder` interface marks a specific point in a series of encoded compute pass commands with a label.

This could be used for telemetry, or may be utilized in `GPUError` messages, browser dev tools, or other services in the future to help with debugging.

Syntax
------

```js
insertDebugMarker(markerLabel)
```

### Parameters

`markerLabel`

A string representing the label to insert.

### Return value

None (`Undefined`).

Examples
--------

```js
// ...

passEncoder.insertDebugMarker("mymarker");

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpudebugcommandsmixin-insertdebugmarker |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: pushDebugGroup() method - Web APIs

GPUComputePassEncoder: pushDebugGroup() method
==============================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`pushDebugGroup()`** method of the
 `GPUComputePassEncoder` interface begins a compute pass debug group, which is marked with a specified label, and will contain all subsequent encoded commands up until a `popDebugGroup()` method is invoked.

This could be used for telemetry, or may be utilized in `GPUError` messages, browser dev tools, or other services in the future to help with debugging.

Syntax
------

```js
pushDebugGroup(groupLabel)
```

### Parameters

`groupLabel`

A string representing the label for the debug group.

### Return value

None (`Undefined`).

Examples
--------

```js
// ...

const passEncoder = commandEncoder.beginComputePass();

passEncoder.pushDebugGroup("mygroupmarker"); // Start labeled debug group

passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(Math.ceil(BUFFER\_SIZE / 64));

passEncoder.popDebugGroup();

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpudebugcommandsmixin-pushdebuggroup |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: dispatchWorkgroups() method - Web APIs

GPUComputePassEncoder: dispatchWorkgroups() method
==================================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`dispatchWorkgroups()`** method of the
 `GPUComputePassEncoder` interface dispatches a specific grid of workgroups to perform the work being done by the current `GPUComputePipeline` (i.e. set via `GPUComputePassEncoder.setPipeline()`).

Syntax
------

```js
dispatchWorkgroups(workgroupCountX)
dispatchWorkgroups(workgroupCountX, workgroupCountY)
dispatchWorkgroups(workgroupCountX, workgroupCountY, workgroupCountZ)
```

### Parameters

`workgroupCountX`

The X dimension of the grid of workgroups to dispatch.

`workgroupCountY` Optional

The Y dimension of the grid of workgroups to dispatch. If omitted, `workgroupCountY` defaults to 1.

`workgroupCountZ` Optional

The Z dimension of the grid of workgroups to dispatch. If omitted, `workgroupCountZ` defaults to 1.

**Note:** The X, Y, and Z dimension values passed to `dispatchWorkgroups()` and `GPUComputePassEncoder.dispatchWorkgroupsIndirect()` are the number of workgroups to dispatch for each dimension, not the number of shader invocations to perform across each dimension. This matches the behavior of modern native GPU APIs, but differs from the behavior of OpenCL. This means that if a `GPUShaderModule` defines an entry point with `@workgroup_size(4, 4)`, and work is dispatched to it with the call `passEncoder.dispatchWorkgroups(8, 8);`, the entry point will be invoked 1024 times total — Dispatching a 4 x 4 workgroup 8 times along both the X and Y axes. `4 * 4 * 8 * 8 = 1024`.

### Return value

None (`Undefined`).

### Validation

The following criteria must be met when calling **`dispatchWorkgroups()`**, otherwise a `GPUValidationError` is generated and the `GPUComputePassEncoder` becomes invalid:

* `workgroupCountX`, `workgroupCountY`, and `workgroupCountZ` are all less than or equal to the `GPUDevice`'s `maxComputeWorkgroupsPerDimension` limit.
Examples
--------

In our basic compute demo, several commands are recorded via a `GPUCommandEncoder`. Most of these commands originate from the `GPUComputePassEncoder` created via `beginComputePass()`.

At the start of the code, we set a global buffer size of 1000. Also, note that the workgroup size in the shader is set to 64.

```js
const BUFFER\_SIZE = 1000;

// Compute shader
const shader = `
@group(0) @binding(0)
var<storage, read\_write> output: array<f32>;

@compute @workgroup\_size(64)

...

`;
```

Later in the code, the `dispatchWorkgroups()` `workgroupCountX` parameter is set based on the global buffer size and the shader workgroup count.

```js
// ...

// Create GPUCommandEncoder to encode commands to issue to the GPU
const commandEncoder = device.createCommandEncoder();

// Initiate render pass
const passEncoder = commandEncoder.beginComputePass();

// Issue commands
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(Math.ceil(BUFFER\_SIZE / 64));

// End the render pass
passEncoder.end();

// Copy output buffer to staging buffer
commandEncoder.copyBufferToBuffer(
  output,
  0, // Source offset
  stagingBuffer,
  0, // Destination offset
  BUFFER\_SIZE,
);

// End frame by passing array of command buffers to command queue for execution
device.queue.submit([commandEncoder.finish()]);

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpucomputepassencoder-dispatchworkgroups |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: popDebugGroup() method - Web APIs

GPUComputePassEncoder: popDebugGroup() method
=============================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`popDebugGroup()`** method of the
 `GPUComputePassEncoder` interface ends a compute pass debug group, which is begun with a `pushDebugGroup()` call.

This could be used for telemetry, or may be utilized in `GPUError` messages, browser dev tools, or other services in the future to help with debugging.

Syntax
------

```js
popDebugGroup()
```

### Parameters

None.

### Return value

None (`Undefined`).

### Validation

The following criteria must be met when calling **`popDebugGroup()`**, otherwise a `GPUValidationError` is generated and the `GPUComputePassEncoder` becomes invalid:

* The compute pass encoder's debug stack is not empty (i.e. at least one compute pass debug group was previously started with `pushDebugGroup()`).
Examples
--------

```js
// ...

const passEncoder = commandEncoder.beginComputePass();

passEncoder.pushDebugGroup("mygroupmarker"); // Start labeled debug group

passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(Math.ceil(BUFFER\_SIZE / 64));

passEncoder.popDebugGroup();

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpudebugcommandsmixin-popdebuggroup |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: dispatchWorkgroupsIndirect() method - Web APIs

GPUComputePassEncoder: dispatchWorkgroupsIndirect() method
==========================================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`dispatchWorkgroupsIndirect()`** method of the
 `GPUComputePassEncoder` interface dispatches a grid of workgroups, defined by the parameters of a `GPUBuffer`, to perform the work being done by the current `GPUComputePipeline` (i.e. set via `GPUComputePassEncoder.setPipeline()`).

Syntax
------

```js
dispatchWorkgroupsIndirect(indirectBuffer, indirectOffset)
```

### Parameters

`indirectBuffer`

A `GPUBuffer` containing the X, Y, and Z dimensions of the grid of workgroups to dispatch. The buffer must contain a tightly packed block of three 32-bit unsigned integer values representing the dimensions (12 bytes total), given in the same order as the arguments for `GPUComputePassEncoder.dispatchWorkgroups()`. So for example:

```js
const uint32 = new Uint32Array(3);
uint32[0] = 25; // The X value
uint32[1] = 1; // The Y value
uint32[2] = 1; // The Z value

// Write values into a GPUBuffer
device.queue.writeBuffer(buffer, 0, uint32, 0, uint32.length);
```

`indirectOffset`

The offset, in bytes, into `indirectBuffer` where the dimension data begins.

**Note:** The X, Y, and Z dimension values passed to `GPUComputePassEncoder.dispatchWorkgroups()` and `dispatchWorkgroupsIndirect()` are the number of workgroups to dispatch for each dimension, not the number of shader invocations to perform across each dimension. This matches the behavior of modern native GPU APIs, but differs from the behavior of OpenCL. This means that if a `GPUShaderModule` defines an entry point with `@workgroup_size(4, 4)`, and work is dispatched to it with the call `dispatchWorkgroupsIndirect(indirectBuffer);` with `indirectBuffer` specifying X and Y dimensions of 8 and 8, the entry point will be invoked 1024 times total — Dispatching a 4 x 4 workgroup 8 times along both the X and Y axes. `4 * 4 * 8 * 8 = 1024`.

### Return value

None (`Undefined`).

### Validation

The following criteria must be met when calling **`dispatchWorkgroupsIndirect()`**, otherwise a `GPUValidationError` is generated and the `GPUComputePassEncoder` becomes invalid:

* `indirectBuffer`'s `GPUBuffer.usage` contains the `GPUBufferUsage.INDIRECT` flag.
* `indirectOffset` + the total size specified by the `X`, `Y`, and `Z` dimensions is less than or equal to the `indirectBuffer`'s `GPUBuffer.size`.
* `indirectOffset` is a multiple of 4.
Examples
--------

```js
// Set global buffer size
const BUFFER\_SIZE = 1000;

// Compute shader; note workgroup size of 64
const shader = `
@group(0) @binding(0)
var<storage, read\_write> output: array<f32>;

@compute @workgroup\_size(64)

...

`;

// ...

// Create GPUCommandEncoder to encode commands to issue to the GPU
const commandEncoder = device.createCommandEncoder();

// Initiate render pass
const passEncoder = commandEncoder.beginComputePass();

// Issue commands
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);

const uint32 = new Uint32Array(3);
// Note workgroupCountX is set based on the global buffer size and the shader workgroup count.
uint32[0] = Math.ceil(BUFFER\_SIZE / 64);
uint32[1] = 1;
uint32[2] = 1;

const workgroupDimensions = device.createBuffer({
  size: 12,
  usage: GPUBufferUsage.COPY\_DST | GPUBufferUsage.INDIRECT,
});
device.queue.writeBuffer(workgroupDimensions, 0, uint32, 0, uint32.length);

passEncoder.dispatchWorkgroupsIndirect(workgroupDimensions, 0);

// End the render pass
passEncoder.end();

// Copy output buffer to staging buffer
commandEncoder.copyBufferToBuffer(
  output,
  0, // Source offset
  stagingBuffer,
  0, // Destination offset
  BUFFER\_SIZE,
);

// End frame by passing array of command buffers to command queue for execution
device.queue.submit([commandEncoder.finish()]);

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpucomputepassencoder-dispatchworkgroupsindirect |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: label property - Web APIs

GPUComputePassEncoder: label property
=====================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`label`** read-only property of the
 `GPUComputePassEncoder` interface is a string providing a label that can be used to identify the object, for example in `GPUError` messages or console warnings.

This can be set by providing a `label` property in the descriptor object passed into the originating `GPUCommandEncoder.beginComputePass()` call, or you can get and set it directly on the `GPUComputePassEncoder` object.

Value
-----

A string. If no label value has previously been set, getting the label returns an empty string.

Examples
--------

Setting and getting a label via `GPUComputePassEncoder.label`:

```js
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();

passEncoder.label = "mycomputepassencoder";
console.log(passEncoder.label); // "mycomputepassencoder"
```

Setting a label via the originating `GPUCommandEncoder.beginComputePass()` call, and then getting it via `GPUComputePassEncoder.label`:

```js
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass({
  label: "mycomputepassencoder",
});

console.log(passEncoder.label); // "mycomputepassencoder"
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpuobjectbase-label |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: setBindGroup() method - Web APIs

GPUComputePassEncoder: setBindGroup() method
============================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`setBindGroup()`** method of the
 `GPUComputePassEncoder` interface sets the `GPUBindGroup` to use for subsequent compute commands, for a given index.

Syntax
------

```js
setBindGroup(index, bindGroup)
setBindGroup(index, bindGroup, dynamicOffsets)
setBindGroup(index, bindGroup, dynamicOffsets, dynamicOffsetsStart,
             dynamicOffsetsLength)
```

### Parameters

`index`

The index to set the bind group at. This matches the `n` index value of the corresponding `@group(n)` attribute in the shader code (`GPUShaderModule`) used in the related pipeline.

`bindGroup`

The `GPUBindGroup` to use for subsequent compute commands.

`dynamicOffsets` Optional

A value specifying the offset, in bytes, for each entry in `bindGroup` with `hasDynamicOffset: true` set (i.e. in the descriptor of the `GPUDevice.createBindGroupLayout()` call that created the `GPUBindGroupLayout` object that the `bindGroup` is based on). This value can be:

* An array of numbers specifying the different offsets.
* A `Uint32Array` containing numbers specifying the offsets.

If a `Uint32Array` value is specified for `dynamicOffsets`, both of the following parameters are also required:

`dynamicOffsetsStart`

A number specifying the offset, in array elements, into `dynamicOffsetsData`, where the dynamic offset data begins.

`dynamicOffsetsLength`

A number specifying the number of dynamic offset values to be read from in `dynamicOffsetsData`.

### Return value

None (`Undefined`).

### Exceptions

For `setBindGroup()` calls that use a `Uint32Array` value for `dynamicOffsets`, the call will throw with a `RangeError` `DOMException` if:

* `dynamicOffsetsStart` is less than 0.
* `dynamicOffsetsStart` + `dynamicOffsetsLength` is greater than `dynamicOffsets.length`.
### Validation

The following criteria must be met when calling **`dispatchWorkgroups()`**, otherwise a `GPUValidationError` is generated and the `GPUComputePassEncoder` becomes invalid:

* `index` is less than or equal to the `GPUDevice`'s `maxBindGroups` limit.
* `dynamicOffsets.length` is the same as the number of entries in `bindGroup` with `hasDynamicOffset: true` set.
* For `bindGroup` entries where the bound `buffer`'s `type` is `"uniform"` (see `GPUDevice.createBindGroupLayout()`), each number in `dynamicOffsets` is a multiple of the `GPUDevice`'s `minUniformBufferOffsetAlignment` limit.
* For `bindGroup` entries where the bound `buffer`'s `type` is `"storage"` or `"read-only-storage"` (see `GPUDevice.createBindGroupLayout()`), each number in `dynamicOffsets` is a multiple of the `GPUDevice`'s `minStorageBufferOffsetAlignment` limit.
* For each `bindGroup` entry, the bound `buffer`'s `offset`, plus the corresponding layout entry's `minBindingSize`, plus the corresponding dynamic offset specified in `dynamicOffsets`, is less than or equal to the bound `buffer`'s `size`.
Examples
--------

In our basic compute demo, several commands are recorded via a `GPUCommandEncoder`. Most of these commands originate from the `GPUComputePassEncoder` created via `beginComputePass()`. The `setBindGroup()` call used here is the simplest form, just specifying index and bind group.

```js
const BUFFER\_SIZE = 1000;

// ...

// Create GPUCommandEncoder to encode commands to issue to the GPU
const commandEncoder = device.createCommandEncoder();

// Initiate render pass
const passEncoder = commandEncoder.beginComputePass();

// Issue commands
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(Math.ceil(BUFFER\_SIZE / 64));

// End the render pass
passEncoder.end();

// Copy output buffer to staging buffer
commandEncoder.copyBufferToBuffer(
  output,
  0, // Source offset
  stagingBuffer,
  0, // Destination offset
  BUFFER\_SIZE,
);

// End frame by passing array of command buffers to command queue for execution
device.queue.submit([commandEncoder.finish()]);

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # programmable-passes-bind-groups |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: setPipeline() method - Web APIs

GPUComputePassEncoder: setPipeline() method
===========================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`setPipeline()`** method of the
 `GPUComputePassEncoder` interface sets the `GPUComputePipeline` to use for this compute pass.

Syntax
------

```js
setPipeline(pipeline)
```

### Parameters

`pipeline`

The `GPUComputePipeline` to use for this compute pass.

### Return value

None (`Undefined`).

Examples
--------

In our basic compute demo, several commands are recorded via a `GPUCommandEncoder`. Most of these commands originate from the `GPUComputePassEncoder` created via `beginComputePass()`. The `setPipeline()` call is used as appropriate to set the pipeline to use for this pass.

```js
const BUFFER\_SIZE = 1000;

// ...

// Create GPUCommandEncoder to encode commands to issue to the GPU
const commandEncoder = device.createCommandEncoder();

// Initiate render pass
const passEncoder = commandEncoder.beginComputePass();

// Issue commands
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(Math.ceil(BUFFER\_SIZE / 64));

// End the render pass
passEncoder.end();

// Copy output buffer to staging buffer
commandEncoder.copyBufferToBuffer(
  output,
  0, // Source offset
  stagingBuffer,
  0, // Destination offset
  BUFFER\_SIZE,
);

// End frame by passing array of command buffers to command queue for execution
device.queue.submit([commandEncoder.finish()]);

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpucomputepassencoder-setpipeline |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


GPUComputePassEncoder: end() method - Web APIs

GPUComputePassEncoder: end() method
===================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

 The **`end()`** method of the
 `GPUComputePassEncoder` interface completes recording of the current compute pass command sequence.

Syntax
------

```js
end()
```

### Parameters

None.

### Return value

None (`Undefined`).

### Validation

The following criteria must be met when calling **`end()`**, otherwise a `GPUValidationError` is generated and the `GPUComputePassEncoder` becomes invalid:

* The `GPUComputePassEncoder` is open (i.e. not already ended via an `end()` call).
* any `pushDebugGroup()` calls made on this encoder have a corresponding `popDebugGroup()` call before `end()` is called.
Examples
--------

In our basic compute demo, several commands are recorded via a `GPUCommandEncoder`. Most of these commands originate from the `GPUComputePassEncoder` created via `GPUCommandEncoder.beginComputePass()`.

```js
const BUFFER\_SIZE = 1000;

// ...

// Create GPUCommandEncoder to encode commands to issue to the GPU
const commandEncoder = device.createCommandEncoder();

// Initiate render pass
const passEncoder = commandEncoder.beginComputePass();

// Issue commands
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(Math.ceil(BUFFER\_SIZE / 64));

// End the render pass
passEncoder.end();

// Copy output buffer to staging buffer
commandEncoder.copyBufferToBuffer(
  output,
  0, // Source offset
  stagingBuffer,
  0, // Destination offset
  BUFFER\_SIZE,
);

// End frame by passing array of command buffers to command queue for execution
device.queue.submit([commandEncoder.finish()]);

// ...
```

Specifications
--------------

| Specification |
| --- |
| WebGPU # dom-gpucomputepassencoder-end |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* The WebGPU API


MutationEvent: attrChange property - Web APIs

MutationEvent: attrChange property
==================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

The **`attrChange`** read-only property of the `MutationEvent` interface returns a number indicating what kind of change triggered the `DOMAttrModified` event. The three possible values are `MODIFICATION` (`1`), `ADDITION` (`2`) or `REMOVAL` (`3`). It has no meaning for other events and is then set to `0`.

Value
-----

An integer: `0`, `1` (`MODIFICATION`), `2` (`ADDITION`), or `3` (`REMOVAL`).

Examples
--------

```js
element.addEventListener(
  "DOMAttrModified",
  (event) => {
    console.log(event.attrChange);
  },
  false,
);
```

Specifications
--------------

| Specification |
| --- |
| UI Events # dom-mutationevent-attrchange |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MutationEvent: prevValue property - Web APIs

MutationEvent: prevValue property
=================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

The **`prevValue`** read-only property of the `MutationEvent` interface returns a string. In `DOMAttrModified` events, it represents the previous value of the `Attr` node. In `DOMCharacterDataModified` events, it contains the previous value of the `CharacterData` node. In all other cases, returns the empty string (`""`).

Value
-----

A string.

Examples
--------

```js
element.addEventListener(
  "DOMAttrModified",
  (event) => {
    console.log(event.previousValue);
  },
  false,
);
```

Specifications
--------------

| Specification |
| --- |
| UI Events # dom-mutationevent-prevvalue |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MutationEvent: relatedNode property - Web APIs

MutationEvent: relatedNode property
===================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

The **`relatedNode`** read-only property of the `MutationEvent` interface returns a string indicating the node related to the event, like the changed node inside the subtree for `DOMSubtreeModified`.

Value
-----

A string.

Examples
--------

```js
element.addEventListener(
  "DOMSubtreeModified",
  (event) => {
    console.log(event.relatedNode);
  },
  false,
);
```

Specifications
--------------

| Specification |
| --- |
| UI Events # dom-mutationevent-relatednode |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MutationEvent: newValue property - Web APIs

MutationEvent: newValue property
================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

The **`newValue`** read-only property of the `MutationEvent` interface returns a string. In `DOMAttrModified` events, it represents the new value of the `Attr` node. In `DOMCharacterDataModified` events, it contains the new value of the `CharacterData` node. In all other cases, returns the empty string (`""`).

Value
-----

A string.

Examples
--------

```js
element.addEventListener(
  "DOMAttrModified",
  (event) => {
    console.log(event.newValue);
  },
  false,
);
```

Specifications
--------------

| Specification |
| --- |
| UI Events # dom-mutationevent-newvalue |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MutationEvent: attrName property - Web APIs

MutationEvent: attrName property
================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

The **`attrName`** read-only property of the `MutationEvent` interface returns a string with the name of the node affected by the `DOMAttrModified` event. It has no meaning for other events and is then set to the empty string (`""`).

Value
-----

A string.

Examples
--------

```js
element.addEventListener(
  "DOMAttrModified",
  (event) => {
    console.log(event.attrName);
  },
  false,
);
```

Specifications
--------------

| Specification |
| --- |
| UI Events # dom-mutationevent-attrname |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


MutationEvent: initMutationEvent() method - Web APIs

MutationEvent: initMutationEvent() method
=========================================

**Deprecated:** This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes. Avoid using it, and update existing code if possible; see the compatibility table at the bottom of this page to guide your decision. Be aware that this feature may cease to work at any time.

 The **`initMutationEvent()`** method of the `MutationEvent` interface initializes the
 value of a mutation event once it's been created (normally using the `Document.createEvent()` method).

This method must be called to set the event before it is dispatched, using `EventTarget.dispatchEvent()`.

**Note:** In general, you won't create these events yourself; they are created by the browser.

Syntax
------

```js
initMutationEvent(type, canBubble, cancelable, relatedNode,
                  prevValue, newValue, attrName, attrChange)
```

### Parameters

`type`

 A string to set the event's `type` to. Browsers set the following values for `MutationEvent`:
 `DOMAttrModified`, `DOMAttributeNameChanged`, `DOMCharacterDataModified`, `DOMElementNameChanged`, `DOMNodeInserted`, `DOMNodeInsertedIntoDocument`, `DOMNodeRemoved`, `DOMNodeRemovedFromDocument`,`DOMSubtreeModified`.

`canBubble`

A boolean indicating whether or not the event can bubble. Sets the value of `Event.bubbles`.

`cancelable`

A boolean indicating whether or not the event's default action can be prevented. Sets the value of `Event.cancelable`.

`relatedNode`

A string representing the new value of the modified node, if any. Sets the value of `MutationEvent.relatedNode`.

`prevValue`

A string representing the previous value of the modified node, if any. Sets the value of `MutationEvent.prevValue`.

`newValue`

A string representing the new value of the modified node, if any. Sets the value of `MutationEvent.newValue`.

`attrName`

A string representing the name of the `Attr` node changed, if any. Sets the value of `MutationEvent.attrName`.

`attrChange`

A integer representing the reason attribute node changed. Sets the value of `MutationEvent.attrChange`.

### Return value

None (`undefined`).

Specifications
--------------

| Specification |
| --- |
| UI Events # dom-mutationevent-initmutationevent |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.


PerformanceResourceTiming: domainLookupStart property - Web APIs

PerformanceResourceTiming: domainLookupStart property
=====================================================

The **`domainLookupStart`** read-only property returns the `timestamp` immediately before the browser starts the domain name lookup for the resource.

Value
-----

The `domainLookupStart` property can have the following values:

* A `DOMHighResTimeStamp` immediately before the browser starts the domain name lookup for the resource.
* `0` if the resource was instantaneously retrieved from a cache.
* `0` if the resource is a cross-origin request and no `Timing-Allow-Origin` HTTP response header is used.
Examples
--------

### Measuring DNS lookup time

The `domainLookupStart` and `domainLookupEnd` properties can be used to measure how long it takes for the DNS lookup to happen.

```js
const dns = entry.domainLookupEnd - entry.domainLookupStart;
```

Example using a `PerformanceObserver`, which notifies of new `resource` performance entries as they are recorded in the browser's performance timeline. Use the `buffered` option to access entries from before the observer creation.

```js
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    const dns = entry.domainLookupEnd - entry.domainLookupStart;
    if (dns > 0) {
      console.log(`${entry.name}: DNS lookup duration: ${dns}ms`);
    }
  });
});

observer.observe({ type: "resource", buffered: true });
```

Example using `Performance.getEntriesByType()`, which only shows `resource` performance entries present in the browser's performance timeline at the time you call this method:

```js
const resources = performance.getEntriesByType("resource");
resources.forEach((entry) => {
  const dns = entry.domainLookupEnd - entry.domainLookupStart;
  if (dns > 0) {
    console.log(`${entry.name}: DNS lookup duration: ${dns}ms`);
  }
});
```

### Cross-origin timing information

If the value of the `domainLookupStart` property is `0`, the resource might be a cross-origin request. To allow seeing cross-origin timing information, the `Timing-Allow-Origin` HTTP response header needs to be set.

For example, to allow `https://developer.mozilla.org` to see timing resources, the cross-origin resource should send:

```http
Timing-Allow-Origin: https://developer.mozilla.org
```

Specifications
--------------

| Specification |
| --- |
| Resource Timing # dom-performanceresourcetiming-domainlookupstart |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `Timing-Allow-Origin`


PerformanceResourceTiming: responseStatus property - Web APIs

PerformanceResourceTiming: responseStatus property
==================================================

**Experimental:** **This is an experimental technology**
Check the Browser compatibility table carefully before using this in production.

The **`responseStatus`** read-only property represents the HTTP response status code returned when fetching the resource.

This property maps to `Response.status` from the Fetch API.

Value
-----

The `responseStatus` property can have the following values:

* A number indicating the HTTP response status code returned when fetching the resource.
* `0` if the CORS check fails.
* `0` for cross-origin `<iframe>` objects.
Examples
--------

### Checking if a cache was hit

The `responseStatus` property can be used to check for cached resources with a `304` `Not Modified` response status code.

Example using a `PerformanceObserver`, which notifies of new `resource` performance entries as they are recorded in the browser's performance timeline. Use the `buffered` option to access entries from before the observer creation.

```js
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.responseStatus === 304) {
      console.log(`${entry.name} was loaded from cache`);
    }
  });
});

observer.observe({ type: "resource", buffered: true });
```

Example using `Performance.getEntriesByType()`, which only shows `resource` performance entries present in the browser's performance timeline at the time you call this method:

```js
const resources = performance.getEntriesByType("resource");
resources.forEach((entry) => {
  if (entry.responseStatus === 304) {
    console.log(`${entry.name} was loaded from cache`);
  }
});
```

Alternatively, if `responseStatus` is not available, you can check if the `transferSize` property returned `0`.

### Cross-origin response status codes

If the value of the `responseStatus` property is `0`, the resource might be a cross-origin request. To allow seeing cross-origin response status codes, the CORS `Access-Control-Allow-Origin` HTTP response header needs to be set.

For example, to allow `https://developer.mozilla.org` to see response status codes, the cross-origin resource should send:

```http
Access-Control-Allow-Origin: https://developer.mozilla.org
```

Specifications
--------------

| Specification |
| --- |
| Resource Timing # dom-performanceresourcetiming-responsestatus |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* HTTP response status code
* `Response.status`
* CORS


PerformanceResourceTiming: domainLookupEnd property - Web APIs

PerformanceResourceTiming: domainLookupEnd property
===================================================

The **`domainLookupEnd`** read-only property returns the `timestamp` immediately after the browser finishes the domain-name lookup for the resource.

If the user agent has the domain information in cache, `domainLookupStart` and `domainLookupEnd` represent the times when the user agent starts and ends the domain data retrieval from the cache.

Value
-----

The `domainLookupEnd` property can have the following values:

* A `DOMHighResTimeStamp` representing the time immediately after the browser finishes the domain name lookup for the resource.
* `0` if the resource was instantaneously retrieved from a cache.
* `0` if the resource is a cross-origin request and no `Timing-Allow-Origin` HTTP response header is used.
Examples
--------

### Measuring DNS lookup time

The `domainLookupEnd` and `domainLookupStart` properties can be used to measure how long it takes for the DNS lookup to happen.

```js
const dns = entry.domainLookupEnd - entry.domainLookupStart;
```

Example using a `PerformanceObserver`, which notifies of new `resource` performance entries as they are recorded in the browser's performance timeline. Use the `buffered` option to access entries from before the observer creation.

```js
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    const dns = entry.domainLookupEnd - entry.domainLookupStart;
    if (dns > 0) {
      console.log(`${entry.name}: DNS lookup duration: ${dns}ms`);
    }
  });
});

observer.observe({ type: "resource", buffered: true });
```

Example using `Performance.getEntriesByType()`, which only shows `resource` performance entries present in the browser's performance timeline at the time you call this method:

```js
const resources = performance.getEntriesByType("resource");
resources.forEach((entry) => {
  const dns = entry.domainLookupEnd - entry.domainLookupStart;
  if (dns > 0) {
    console.log(`${entry.name}: DNS lookup duration: ${dns}ms`);
  }
});
```

### Cross-origin timing information

If the value of the `domainLookupEnd` property is `0`, the resource might be a cross-origin request. To allow seeing cross-origin timing information, the `Timing-Allow-Origin` HTTP response header needs to be set.

For example, to allow `https://developer.mozilla.org` to see timing resources, the cross-origin resource should send:

```http
Timing-Allow-Origin: https://developer.mozilla.org
```

Specifications
--------------

| Specification |
| --- |
| Resource Timing # dom-performanceresourcetiming-domainlookupend |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `Timing-Allow-Origin`


PerformanceResourceTiming: workerStart property - Web APIs

PerformanceResourceTiming: workerStart property
===============================================

 The **`workerStart`** read-only property of the `PerformanceResourceTiming` interface returns a
 `DOMHighResTimeStamp` immediately before dispatching the `FetchEvent` if a Service Worker thread is already running, or immediately before starting the Service Worker thread if it is not already running. If the resource is not intercepted by a Service Worker the property will always return 0.

Value
-----

The `workerStart` property can have the following values:

* A `DOMHighResTimeStamp`.
* `0` if no service worker is used.
* `0` if the resource is a cross-origin request and no `Timing-Allow-Origin` HTTP response header is used.
Examples
--------

### Measuring ServiceWorker processing time

The `workerStart` and `fetchStart` properties can be used to measure the processing time of a `ServiceWorker`.

```js
const workerProcessingTime = entry.fetchStart - entry.workerStart;
```

Example using a `PerformanceObserver`, which notifies of new `resource` performance entries as they are recorded in the browser's performance timeline. Use the `buffered` option to access entries from before the observer creation.

```js
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    const workerProcessingTime = entry.fetchStart - entry.workerStart;
    if (workerProcessingTime > 0) {
      console.log(
        `${entry.name}: Worker processing time: ${workerProcessingTime}ms`,
      );
    }
  });
});

observer.observe({ type: "resource", buffered: true });
```

Example using `Performance.getEntriesByType()`, which only shows `resource` performance entries present in the browser's performance timeline at the time you call this method:

```js
const resources = performance.getEntriesByType("resource");
resources.forEach((entry) => {
  const workerProcessingTime = entry.fetchStart - entry.workerStart;
  if (workerProcessingTime > 0) {
    console.log(
      `${entry.name}: Worker processing time: ${workerProcessingTime}ms`,
    );
  }
});
```

### Cross-origin timing information

If the value of the `workerStart` property is `0`, the resource might be a cross-origin request. To allow seeing cross-origin timing information, the `Timing-Allow-Origin` HTTP response header needs to be set.

For example, to allow `https://developer.mozilla.org` to see timing resources, the cross-origin resource should send:

```http
Timing-Allow-Origin: https://developer.mozilla.org
```

Specifications
--------------

| Specification |
| --- |
| Resource Timing # dom-performanceresourcetiming-workerstart |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `Timing-Allow-Origin`


PerformanceResourceTiming: responseStart property - Web APIs

PerformanceResourceTiming: responseStart property
=================================================

The **`responseStart`** read-only property returns a `timestamp` immediately after the browser receives the first byte of the response from the server, cache, or local resource.

Value
-----

The `responseStart` property can have the following values:

* A `DOMHighResTimeStamp` immediately after the browser receives the first byte of the response from the server.
* `0` if the resource was instantaneously retrieved from a cache.
* `0` if the resource is a cross-origin request and no `Timing-Allow-Origin` HTTP response header is used.
Examples
--------

### Measuring request time

The `responseStart` and `requestStart` properties can be used to measure how long the request takes.

```js
const request = entry.responseStart - entry.requestStart;
```

Example using a `PerformanceObserver`, which notifies of new `resource` performance entries as they are recorded in the browser's performance timeline. Use the `buffered` option to access entries from before the observer creation.

```js
const observer = new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    const request = entry.responseStart - entry.requestStart;
    if (request > 0) {
      console.log(`${entry.name}: Request time: ${request}ms`);
    }
  });
});

observer.observe({ type: "resource", buffered: true });
```

Example using `Performance.getEntriesByType()`, which only shows `resource` performance entries present in the browser's performance timeline at the time you call this method:

```js
const resources = performance.getEntriesByType("resource");
resources.forEach((entry) => {
  const request = entry.responseStart - entry.requestStart;
  if (request > 0) {
    console.log(`${entry.name}: Request time: ${request}ms`);
  }
});
```

### Cross-origin timing information

If the value of the `responseStart` property is `0`, the resource might be a cross-origin request. To allow seeing cross-origin timing information, the `Timing-Allow-Origin` HTTP response header needs to be set.

For example, to allow `https://developer.mozilla.org` to see timing resources, the cross-origin resource should send:

```http
Timing-Allow-Origin: https://developer.mozilla.org
```

Specifications
--------------

| Specification |
| --- |
| Resource Timing # dom-performanceresourcetiming-responsestart |

Browser compatibility
---------------------

BCD tables only load in the browser with JavaScript enabled. Enable JavaScript to view data.

See also
--------

* `Timing-Allow-Origin`