diff --git a/api-samples/idle/README.md b/api-samples/idle/README.md
new file mode 100644
index 00000000..bf92ebb5
--- /dev/null
+++ b/api-samples/idle/README.md
@@ -0,0 +1,19 @@
+# chrome.idle
+
+This sample demonstrates how to use the [`chrome.idle`](https://developer.chrome.com/docs/extensions/reference/idle/) API.
+
+## Overview
+
+In this sample, the `chrome.idle` API detects and stores the history of the user's idle state.
+
+## Implementation Notes
+
+The detection interval of [`chrome.idle.onStateChanged`](https://developer.chrome.com/docs/extensions/reference/idle/#event-onStateChanged) event needs to be modified using the [`chrome.idle.setDetectionInterval`](https://developer.chrome.com/docs/extensions/reference/idle/#method-setDetectionInterval) method.
+
+The idle state history is stored in the [`chrome.storage.session`](https://developer.chrome.com/docs/extensions/reference/storage/#property-session) storage area.
+
+## Running this extension
+
+1. Clone this repository.
+2. Load this directory in Chrome as an [unpacked extension](https://developer.chrome.com/docs/extensions/mv3/getstarted/development-basics/#load-unpacked).
+3. Click the extension's action icon to open the window.
diff --git a/api-samples/idle/history.html b/api-samples/idle/history.html
new file mode 100644
index 00000000..ddabf029
--- /dev/null
+++ b/api-samples/idle/history.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
Idle API Demonstration
+
Current state
+
+ Idle threshold:
+
+
+
+
+ chrome.idle.queryState(,
+ ...);
+ -
+
+
+
Last state change:
+
+
Idle changes:
+
+
+
+
diff --git a/api-samples/idle/history.js b/api-samples/idle/history.js
new file mode 100644
index 00000000..c2c41062
--- /dev/null
+++ b/api-samples/idle/history.js
@@ -0,0 +1,102 @@
+/**
+ * Convert a state and time into a nice styled chunk of HTML.
+ */
+function renderState(state, time) {
+ const now = Date.now();
+ const diff = Math.round((time - now) / 1000);
+ const str =
+ diff == 0
+ ? 'now'
+ : Math.abs(diff) + ' seconds ' + (diff > 0 ? 'from now' : 'ago');
+ const col = state == 'active' ? '#009900' : '#990000';
+ return "" + state + ' ' + str;
+}
+
+/**
+ * Creates DOM and injects a rendered state into the page.
+ */
+function renderItem(state, time, parent) {
+ const dom_item = document.createElement('li');
+ dom_item.innerHTML = renderState(state, time);
+ parent.appendChild(dom_item);
+}
+
+// Store previous state so we can show deltas. This is important
+// because the API currently doesn't fire idle messages, and we'd
+// like to keep track of last time we went idle.
+let laststate = null;
+let laststatetime = null;
+
+/**
+ * Checks the current state of the browser.
+ */
+async function checkState() {
+ const threshold = parseInt(document.querySelector('#idle-threshold').value);
+ const dom_threshold = document.querySelector('#idle-set-threshold');
+ dom_threshold.innerText = threshold;
+
+ // Request the state based off of the user-supplied threshold.
+ chrome.idle.queryState(threshold, function (state) {
+ const time = new Date();
+ if (laststate != state) {
+ laststate = state;
+ laststatetime = time;
+ }
+
+ // Keep rendering results so we get a nice "seconds elapsed" view.
+ const dom_result = document.querySelector('#idle-state');
+ dom_result.innerHTML = renderState(state, time);
+ const dom_laststate = document.querySelector('#idle-laststate');
+ dom_laststate.innerHTML = renderState(laststate, laststatetime);
+ });
+}
+
+/**
+ * Render the data gathered by the background service worker - should show a log
+ * of "active" states.
+ */
+async function renderHistory() {
+ const dom_history = document.querySelector('#idle-history');
+ dom_history.innerHTML = '';
+ const { history_log } = await chrome.storage.session.get(['history_log']);
+ if (!history_log) {
+ return;
+ }
+
+ for (let i = 0; i < history_log.length; i++) {
+ const data = history_log[i];
+ renderItem(data['state'], data['time'], dom_history);
+ }
+}
+
+document.addEventListener('DOMContentLoaded', async function () {
+ // Set the threshold to the last value the user set, or 15 if not set.
+ let { threshold: stored_threshold } = await chrome.storage.local.get([
+ 'threshold'
+ ]);
+ if (!stored_threshold || ![15, 30, 60].includes(stored_threshold)) {
+ stored_threshold = 15;
+ }
+
+ document.querySelector(
+ `#idle-threshold option[value="${stored_threshold}"]`
+ ).selected = true;
+ chrome.idle.setDetectionInterval(stored_threshold);
+
+ document
+ .querySelector('#idle-threshold')
+ .addEventListener('change', function (e) {
+ const threshold = parseInt(e.target.value);
+ chrome.storage.local.set({ threshold: threshold });
+ chrome.idle.setDetectionInterval(threshold);
+ });
+
+ // Check every second (even though this is overkill - minimum idle
+ // threshold is 15 seconds) so that the numbers appear to be counting up.
+ checkState();
+ window.setInterval(checkState, 1000);
+
+ // Check every second (see above).
+ renderHistory();
+ window.setInterval(renderHistory, 1000);
+});
diff --git a/api-samples/idle/manifest.json b/api-samples/idle/manifest.json
new file mode 100644
index 00000000..cf777d0f
--- /dev/null
+++ b/api-samples/idle/manifest.json
@@ -0,0 +1,18 @@
+{
+ "name": "Idle - Simple Example",
+ "version": "1.0.1",
+ "description": "Demonstrates the Idle API",
+ "background": {
+ "service_worker": "service-worker.js"
+ },
+ "permissions": ["idle", "storage"],
+ "action": {
+ "default_icon": "sample-19.png"
+ },
+ "icons": {
+ "16": "sample-16.png",
+ "48": "sample-48.png",
+ "128": "sample-128.png"
+ },
+ "manifest_version": 3
+}
diff --git a/api-samples/idle/sample-128.png b/api-samples/idle/sample-128.png
new file mode 100644
index 00000000..96be429f
Binary files /dev/null and b/api-samples/idle/sample-128.png differ
diff --git a/api-samples/idle/sample-16.png b/api-samples/idle/sample-16.png
new file mode 100644
index 00000000..5308fc97
Binary files /dev/null and b/api-samples/idle/sample-16.png differ
diff --git a/api-samples/idle/sample-19.png b/api-samples/idle/sample-19.png
new file mode 100644
index 00000000..108b2a77
Binary files /dev/null and b/api-samples/idle/sample-19.png differ
diff --git a/api-samples/idle/sample-48.png b/api-samples/idle/sample-48.png
new file mode 100644
index 00000000..a732e383
Binary files /dev/null and b/api-samples/idle/sample-48.png differ
diff --git a/api-samples/idle/service-worker.js b/api-samples/idle/service-worker.js
new file mode 100644
index 00000000..8c3473b8
--- /dev/null
+++ b/api-samples/idle/service-worker.js
@@ -0,0 +1,27 @@
+/**
+ * Stores a state every time it changes, up to 20 items.
+ */
+chrome.idle.onStateChanged.addListener(async function (newstate) {
+ let { history_log } = await chrome.storage.session.get(['history_log']);
+ if (!history_log) {
+ history_log = [];
+ }
+ const time = Date.now();
+ if (history_log.length >= 20) {
+ history_log.pop();
+ }
+ history_log.unshift({ state: newstate, time: time });
+ chrome.storage.session.set({ history_log: history_log });
+});
+
+/**
+ * Opens history.html when the browser action is clicked.
+ */
+chrome.action.onClicked.addListener(function () {
+ chrome.windows.create({
+ url: 'history.html',
+ width: 700,
+ height: 600,
+ type: 'popup'
+ });
+});