+
+
+
+
diff --git a/api-samples/tabs/screenshot/screenshot.js b/api-samples/tabs/screenshot/screenshot.js
new file mode 100644
index 00000000..d06f4466
--- /dev/null
+++ b/api-samples/tabs/screenshot/screenshot.js
@@ -0,0 +1,9 @@
+function setScreenshotUrl(url) {
+ document.getElementById('target').src = url;
+}
+
+chrome.runtime.onMessage.addListener(function (request) {
+ if (request.msg === 'screenshot') {
+ setScreenshotUrl(request.data);
+ }
+});
diff --git a/api-samples/tabs/screenshot/service-worker.js b/api-samples/tabs/screenshot/service-worker.js
new file mode 100644
index 00000000..dd492724
--- /dev/null
+++ b/api-samples/tabs/screenshot/service-worker.js
@@ -0,0 +1,24 @@
+// Listen for a click on the camera icon. On that click, take a screenshot.
+chrome.action.onClicked.addListener(async function () {
+ const screenshotUrl = await chrome.tabs.captureVisibleTab();
+ const viewTabUrl = chrome.runtime.getURL('screenshot.html');
+ let targetId = null;
+
+ chrome.tabs.onUpdated.addListener(function listener(tabId, changedProps) {
+ // We are waiting for the tab we opened to finish loading.
+ // Check that the tab's id matches the tab we opened,
+ // and that the tab is done loading.
+ if (tabId != targetId || changedProps.status != 'complete') return;
+
+ // Passing the above test means this is the event we were waiting for.
+ // There is nothing we need to do for future onUpdated events, so we
+ // use removeListner to stop getting called when onUpdated events fire.
+ chrome.tabs.onUpdated.removeListener(listener);
+
+ // Send screenshotUrl to the tab.
+ chrome.tabs.sendMessage(tabId, { msg: 'screenshot', data: screenshotUrl });
+ });
+
+ const tab = await chrome.tabs.create({ url: viewTabUrl });
+ targetId = tab.id;
+});
diff --git a/api-samples/tabs/screenshot/white.png b/api-samples/tabs/screenshot/white.png
new file mode 100644
index 00000000..52b9a9f1
Binary files /dev/null and b/api-samples/tabs/screenshot/white.png differ
diff --git a/api-samples/tabs/zoom/README.md b/api-samples/tabs/zoom/README.md
new file mode 100644
index 00000000..047e888b
--- /dev/null
+++ b/api-samples/tabs/zoom/README.md
@@ -0,0 +1,21 @@
+# chrome.tabs - Tab zoom
+
+A sample that demonstrates how to use the zoom features of the [`chrome.tabs`](https://developer.chrome.com/docs/extensions/reference/tabs/) API.
+
+## Overview
+
+In this sample, the zoom related [`chrome.tabs`](https://developer.chrome.com/docs/extensions/reference/tabs/) APIs are used to change the magnification and zoom mode of the active tab.
+
+## Implementation Notes
+
+- [`chrome.tabs.getZoom()`](https://developer.chrome.com/docs/extensions/reference/tabs/#method-getZoom) returns the current zoom level of the tab.
+- [`chrome.tabs.setZoom()`](https://developer.chrome.com/docs/extensions/reference/tabs/#method-setZoom) changes the zoom level of the tab.
+- [`chrome.tabs.setZoomSettings()`](https://developer.chrome.com/docs/extensions/reference/tabs/#method-setZoomSettings) sets the zoom settings of the tab.
+- [`chrome.tabs.getZoomSettings()`](https://developer.chrome.com/docs/extensions/reference/tabs/#method-getZoomSettings) returns the zoom settings of the tab.
+- [`chrome.tabs.onZoomChange()`](https://developer.chrome.com/docs/extensions/reference/tabs/#event-onZoomChange) listens for zoom changes in the tab.
+
+## 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. Open any website, such as `https://google.com` in a new tab, and then click the extension icon to open the popup.
diff --git a/api-samples/tabs/zoom/manifest.json b/api-samples/tabs/zoom/manifest.json
new file mode 100644
index 00000000..85a8fc5a
--- /dev/null
+++ b/api-samples/tabs/zoom/manifest.json
@@ -0,0 +1,18 @@
+{
+ "manifest_version": 3,
+ "name": "Tabs zoom API Demo",
+ "description": "Uses the tabs.zoom API to manipulate the zoom level of the current tab.",
+ "version": "0.1",
+ "icons": {
+ "16": "zoom16.png",
+ "48": "zoom48.png"
+ },
+ "background": {
+ "service_worker": "service-worker.js"
+ },
+ "action": {
+ "default_icon": "zoom19.png",
+ "default_title": "Zoom Extension Demo",
+ "default_popup": "popup.html"
+ }
+}
diff --git a/api-samples/tabs/zoom/popup.html b/api-samples/tabs/zoom/popup.html
new file mode 100644
index 00000000..64742c9e
--- /dev/null
+++ b/api-samples/tabs/zoom/popup.html
@@ -0,0 +1,78 @@
+
+
+
+ Tab Zoom Extension
+
+
+
+
+
+
+
+
+
+
+ 100%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api-samples/tabs/zoom/popup.js b/api-samples/tabs/zoom/popup.js
new file mode 100644
index 00000000..9eb6bb4d
--- /dev/null
+++ b/api-samples/tabs/zoom/popup.js
@@ -0,0 +1,124 @@
+/**
+ * @fileoverview This code supports the popup behaviour of the extension, and
+ * demonstrates how to:
+ *
+ * 1) Set the zoom for a tab using tabs.setZoom()
+ * 2) Read the current zoom of a tab using tabs.getZoom()
+ * 3) Set the zoom mode of a tab using tabs.setZoomSettings()
+ * 4) Read the current zoom mode of a tab using
+ * tabs.getZoomSettings()
+ *
+ * It also demonstrates using a zoom change listener to update the
+ * contents of a control.
+ */
+
+const zoomStep = 1.1;
+let tabId = -1;
+
+function displayZoomLevel(level) {
+ const percentZoom = parseFloat(level) * 100;
+ const zoom_percent_str = percentZoom.toFixed(1) + '%';
+
+ document.getElementById('displayDiv').textContent = zoom_percent_str;
+}
+
+document.addEventListener('DOMContentLoaded', async function () {
+ // Find the tabId of the current (active) tab. We could just omit the tabId
+ // parameter in the function calls below, and they would act on the current
+ // tab by default, but for the purposes of this demo we will always use the
+ // API with an explicit tabId to demonstrate its use.
+ const tabs = await chrome.tabs.query({ active: true });
+ if (tabs.length > 1)
+ console.log(
+ '[ZoomDemoExtension] Query unexpectedly returned more than 1 tab.'
+ );
+ tabId = tabs[0].id;
+
+ const zoomSettings = await chrome.tabs.getZoomSettings(tabId);
+ const modeRadios = document.getElementsByName('modeRadio');
+ for (let i = 0; i < modeRadios.length; i++) {
+ if (modeRadios[i].value == zoomSettings.mode) modeRadios[i].checked = true;
+ }
+
+ const scopeRadios = document.getElementsByName('scopeRadio');
+ for (let i = 0; i < scopeRadios.length; i++) {
+ if (scopeRadios[i].value == zoomSettings.scope)
+ scopeRadios[i].checked = true;
+ }
+
+ const percentDefaultZoom = parseFloat(zoomSettings.defaultZoomFactor) * 100;
+ document.getElementById('defaultLabel').textContent =
+ 'Default: ' + percentDefaultZoom.toFixed(1) + '%';
+
+ const zoomFactor = await chrome.tabs.getZoom(tabId);
+ displayZoomLevel(zoomFactor);
+
+ document.getElementById('increaseButton').onclick = doZoomIn;
+ document.getElementById('decreaseButton').onclick = doZoomOut;
+ document.getElementById('defaultButton').onclick = doZoomDefault;
+ document.getElementById('setModeButton').onclick = doSetMode;
+ document.getElementById('closeButton').onclick = doClose;
+});
+
+function zoomChangeListener(zoomChangeInfo) {
+ displayZoomLevel(zoomChangeInfo.newZoomFactor);
+}
+
+chrome.tabs.onZoomChange.addListener(zoomChangeListener);
+
+function errorHandler(error) {
+ console.log('[ZoomDemoExtension] ' + error.message);
+}
+
+async function changeZoomByFactorDelta(factorDelta) {
+ if (tabId == -1) return;
+
+ const zoomFactor = await chrome.tabs.getZoom(tabId);
+ const newZoomFactor = factorDelta * zoomFactor;
+ chrome.tabs.setZoom(tabId, newZoomFactor).catch(errorHandler);
+}
+
+function doZoomIn() {
+ changeZoomByFactorDelta(zoomStep);
+}
+
+function doZoomOut() {
+ changeZoomByFactorDelta(1.0 / zoomStep);
+}
+
+function doZoomDefault() {
+ if (tabId == -1) return;
+
+ chrome.tabs.setZoom(tabId, 0).catch(errorHandler);
+}
+
+function doSetMode() {
+ if (tabId == -1) return;
+
+ let modeVal;
+ const modeRadios = document.getElementsByName('modeRadio');
+ for (let i = 0; i < modeRadios.length; i++) {
+ if (modeRadios[i].checked) modeVal = modeRadios[i].value;
+ }
+
+ let scopeVal;
+ const scopeRadios = document.getElementsByName('scopeRadio');
+ for (let i = 0; i < scopeRadios.length; i++) {
+ if (scopeRadios[i].checked) scopeVal = scopeRadios[i].value;
+ }
+
+ if (!modeVal || !scopeVal) {
+ console.log(
+ '[ZoomDemoExtension] Must specify values for both mode & scope.'
+ );
+ return;
+ }
+
+ chrome.tabs
+ .setZoomSettings(tabId, { mode: modeVal, scope: scopeVal })
+ .catch(errorHandler);
+}
+
+function doClose() {
+ self.close();
+}
diff --git a/api-samples/tabs/zoom/service-worker.js b/api-samples/tabs/zoom/service-worker.js
new file mode 100644
index 00000000..6309f42e
--- /dev/null
+++ b/api-samples/tabs/zoom/service-worker.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview In this extension, the background script demonstrates how to
+ * listen for zoom change events.
+ */
+
+function zoomChangeListener(zoomChangeInfo) {
+ const settings_str =
+ 'mode:' +
+ zoomChangeInfo.zoomSettings.mode +
+ ', scope:' +
+ zoomChangeInfo.zoomSettings.scope;
+
+ console.log(
+ '[ZoomDemoExtension] zoomChangeListener(tab=' +
+ zoomChangeInfo.tabId +
+ ', new=' +
+ zoomChangeInfo.newZoomFactor +
+ ', old=' +
+ zoomChangeInfo.oldZoomFactor +
+ ', ' +
+ settings_str +
+ ')'
+ );
+}
+
+chrome.tabs.onZoomChange.addListener(zoomChangeListener);
diff --git a/api-samples/tabs/zoom/zoom16.png b/api-samples/tabs/zoom/zoom16.png
new file mode 100644
index 00000000..d1bdbddf
Binary files /dev/null and b/api-samples/tabs/zoom/zoom16.png differ
diff --git a/api-samples/tabs/zoom/zoom19.png b/api-samples/tabs/zoom/zoom19.png
new file mode 100644
index 00000000..9f22ce22
Binary files /dev/null and b/api-samples/tabs/zoom/zoom19.png differ
diff --git a/api-samples/tabs/zoom/zoom48.png b/api-samples/tabs/zoom/zoom48.png
new file mode 100644
index 00000000..8a4663a9
Binary files /dev/null and b/api-samples/tabs/zoom/zoom48.png differ