diff --git a/api-samples/tabs/inspector/README.md b/api-samples/tabs/inspector/README.md new file mode 100644 index 00000000..d656190e --- /dev/null +++ b/api-samples/tabs/inspector/README.md @@ -0,0 +1,13 @@ +# chrome.tabs - Tab Inspector + +A sample that demonstrates how to use the [`chrome.tabs`](https://developer.chrome.com/docs/extensions/reference/tabs/) API. + +## Overview + +In the sample, a simple tab inspector manipulates the tabs and windows. + +## 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 icon to open the tab inspector. diff --git a/api-samples/tabs/inspector/manifest.json b/api-samples/tabs/inspector/manifest.json new file mode 100644 index 00000000..2a51e464 --- /dev/null +++ b/api-samples/tabs/inspector/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "Tab Inspector", + "description": "Demonstrates the chrome.tabs API and the chrome.windows API by providing a user interface to manage tabs and windows.", + "version": "0.3", + "permissions": ["tabs"], + "background": { + "service_worker": "service-worker.js" + }, + "action": { + "default_title": "Show tab inspector" + }, + "manifest_version": 3 +} diff --git a/api-samples/tabs/inspector/service-worker.js b/api-samples/tabs/inspector/service-worker.js new file mode 100644 index 00000000..34c43131 --- /dev/null +++ b/api-samples/tabs/inspector/service-worker.js @@ -0,0 +1,5 @@ +chrome.action.onClicked.addListener(function () { + chrome.tabs.create({ + url: chrome.runtime.getURL('window_and_tabs_manager.html') + }); +}); diff --git a/api-samples/tabs/inspector/window_and_tabs_manager.css b/api-samples/tabs/inspector/window_and_tabs_manager.css new file mode 100644 index 00000000..c710fad1 --- /dev/null +++ b/api-samples/tabs/inspector/window_and_tabs_manager.css @@ -0,0 +1,110 @@ +.window-item { + background-color: #aaeeee; + margin: 4px; + padding: 8px; + margin: 20px; +} + +.window-id-wrapper { + font-style: italic; + width: 80px; + display: inline-block; +} + +.window_left, +.window_right, +.window_width, +.window_height { + display: inline-block; +} + +.window-status-input { + width: 60px; +} + +.tab-item { + background-color: #eeeeee; + margin: 8px; + padding: 4px; +} + +.wrapper { + margin: 8px; +} + +.tab_id { + font-style: italic; + width: 80px; + display: inline-block; +} + +.tab-actions-wrapper { + display: inline-block; +} + +.tab_index { + width: 20px; +} + +.tab_window_id { + width: 80px; +} + +#log { + background-color: #eeaaee; + margin: 20px; + padding: 8px; +} + +.input-group { + display: flex; + justify-content: space-between; +} + +.label { + display: inline-block; +} + +.tab_title, +.tab_url, +#new_window_url, +#window_id_new, +#url_new { + width: 90%; +} + +.new-window-wrapper { + background-color: #eeeebb; + margin: 20px; + padding: 8px; +} + +.new-tab-wrapper { + background-color: #eeeeaa; + margin: 20px; + padding: 8px; +} + +h3 { + text-align: center; + margin: 8px; +} + +.window_left, +.window_top, +.window_width, +.window_height, +#new_window_left, +#new_window_top, +#new_window_width, +#new_window_height { + width: 50px; +} + +.new-window-status-wrapper { + display: inline-block; +} + +.actions-wrapper { + margin: 20px; +} diff --git a/api-samples/tabs/inspector/window_and_tabs_manager.html b/api-samples/tabs/inspector/window_and_tabs_manager.html new file mode 100644 index 00000000..46b853ed --- /dev/null +++ b/api-samples/tabs/inspector/window_and_tabs_manager.html @@ -0,0 +1,107 @@ + + + + Tabs Inspector + + + + +
+ +
+ + +
+

Create Window

+
+
+ left: + top: + width: + height: + +
+
+
+
+
url:
+ +
+
+ +
+
+

Create Tab

+
+
+
windowId:
+ +
+
+
url:
+ +
+
active
+
+ +
+
+ + + + + +
+
+ + + diff --git a/api-samples/tabs/inspector/window_and_tabs_manager.js b/api-samples/tabs/inspector/window_and_tabs_manager.js new file mode 100644 index 00000000..d67312a5 --- /dev/null +++ b/api-samples/tabs/inspector/window_and_tabs_manager.js @@ -0,0 +1,437 @@ +let tabs = {}; +let tabIds = []; + +let focusedWindowId = undefined; +let currentWindowId = undefined; + +async function bootstrap() { + const currentWindow = await chrome.windows.getCurrent(); + currentWindowId = currentWindow.id; + const focusedWindow = await chrome.windows.getLastFocused(); + focusedWindowId = focusedWindow.id; + loadWindowList(); +} + +function isInt(i) { + return typeof i == 'number' && !(i % 1) && !isNaN(i); +} + +const windowTemplate = document.getElementById('windowItem').content; +const tabTemplate = document.getElementById('tabItem').content; + +async function loadWindowList() { + const windowList = await chrome.windows.getAll({ populate: true }); + tabs = {}; + tabIds = []; + for (let window of windowList) { + for (let tab of window.tabs) { + tabIds.push(tab.id); + tabs[tab.id] = tab; + } + } + + const output = document.getElementById('windowList'); + output.innerHTML = ''; + + for (let window of windowList) { + const windowItem = document.importNode(windowTemplate, true).children[0]; + renderWindow(window, windowItem); + registerWindowEvents(window, windowItem); + + output.appendChild(windowItem); + } +} + +function renderWindow(window, windowItem) { + windowItem.id = `window_${window.id}`; + windowItem.querySelector('.window_left').id = `left_${window.id}`; + windowItem.querySelector('.window_top').id = `top_${window.id}`; + windowItem.querySelector('.window_width').id = `width_${window.id}`; + windowItem.querySelector('.window_height').id = `height_${window.id}`; + windowItem.querySelector('.window_focused').id = `focused_${window.id}`; + windowItem.querySelector('.window_current').id = `current_${window.id}`; + windowItem.querySelector('.window_id').innerText = window.id; + windowItem.querySelector('.window_left').value = window.left; + windowItem.querySelector('.window_top').value = window.top; + windowItem.querySelector('.window_width').value = window.width; + windowItem.querySelector('.window_height').value = window.height; + windowItem.querySelector('.window_focused').checked = + window.id == focusedWindowId; + windowItem.querySelector('.window_current').checked = + window.id == currentWindowId; + + windowItem.querySelector('#tabList').innerHTML = ''; + for (let tab of window.tabs) { + const tabItem = document.importNode(tabTemplate, true).children[0]; + renderTab(tab, tabItem); + registerTabEvents(tab, tabItem); + windowItem.querySelector('#tabList').appendChild(tabItem); + } +} + +function registerWindowEvents(window, windowItem) { + windowItem + .querySelector('.window_refresh') + .addEventListener('click', function () { + refreshWindow(window.id); + }); + + windowItem + .querySelector('.update_window_button') + .addEventListener('click', function () { + updateWindow(window.id); + }); + + windowItem + .querySelector('.remove_window_button') + .addEventListener('click', function () { + removeWindow(window.id); + }); + + windowItem + .querySelector('.refresh_active_tab_button') + .addEventListener('click', function () { + refreshActiveTab(window.id); + }); +} + +function renderTab(tab, tabItem) { + tabItem.id = `tab_${tab.id}`; + tabItem.querySelector('.tab_index').id = `index_${tab.id}`; + tabItem.querySelector('.tab_window_id').id = `windowId_${tab.id}`; + tabItem.querySelector('.tab_title').id = `title_${tab.id}`; + tabItem.querySelector('.tab_url').id = `url_${tab.id}`; + tabItem.querySelector('.tab_active').id = `active_${tab.id}`; + + tabItem.querySelector('.tab_id').innerText = `TabId: ${tab.id}`; + tabItem.querySelector('.tab_index').value = tab.index; + tabItem.querySelector('.tab_window_id').value = tab.windowId; + tabItem.querySelector('.tab_title').value = tab.title; + tabItem.querySelector('.tab_url').value = tab.url; + tabItem.querySelector('.tab_active').checked = tab.active; +} + +function registerTabEvents(tab, tabItem) { + tabItem + .querySelector('.move_tab_button') + .addEventListener('click', function () { + moveTab(tab.id); + }); + tabItem + .querySelector('.refresh_tab_button') + .addEventListener('click', function () { + refreshTab(tab.id); + }); + tabItem + .querySelector('.update_tab_button') + .addEventListener('click', function () { + updateTab(tab.id); + }); + tabItem + .querySelector('.remove_tab_button') + .addEventListener('click', function () { + removeTab(tab.id); + }); + tabItem + .querySelector('.tab_active') + .addEventListener('change', function (event) { + const active = event.target.checked; + const tabId = parseInt(event.target.id.split('_')[1]); + chrome.tabs.update(tabId, { active }); + }); +} + +function updateTabData(id) { + const retval = { + url: document.getElementById('url_' + id).value, + active: document.getElementById('active_' + id).value ? true : false + }; + + return retval; +} + +async function updateTab(id) { + try { + await chrome.tabs.update(id, updateTabData(id)); + } catch (e) { + alert(e); + } +} + +function moveTabData(id) { + return { + index: parseInt(document.getElementById('index_' + id).value), + windowId: parseInt(document.getElementById('windowId_' + id).value) + }; +} + +function moveTab(id) { + chrome.tabs.move(id, moveTabData(id)).catch(alert); +} + +function createTabData() { + return { + windowId: parseInt(document.getElementById('window_id_new').value), + url: document.getElementById('url_new').value, + active: document.getElementById('active_new').checked + }; +} + +function createTab() { + const args = createTabData(); + + if (!isInt(args.windowId)) delete args.windowId; + if (!args.url) delete args.url; + + chrome.tabs.create(args).catch(alert); +} + +document + .getElementById('create_tab_button') + .addEventListener('click', createTab); + +async function updateAll() { + try { + for (let i = 0; i < tabIds.length; i++) { + await chrome.tabs.update(tabIds[i], updateTabData(tabIds[i])); + } + } catch (e) { + alert(e); + } +} + +async function moveAll() { + appendToLog('moving all'); + try { + for (let i = 0; i < tabIds.length; i++) { + await chrome.tabs.move(tabIds[i], moveTabData(tabIds[i])); + } + } catch (e) { + alert(e); + } +} + +function removeTab(tabId) { + chrome.tabs + .remove(tabId) + .then(function () { + appendToLog('tab: ' + tabId + ' removed.'); + }) + .catch(alert); +} + +function appendToLog(logLine) { + document + .getElementById('log') + .appendChild(document.createElement('div')).innerText = '> ' + logLine; +} + +function clearLog() { + document.getElementById('log').innerText = ''; +} + +chrome.windows.onCreated.addListener(function (createInfo) { + appendToLog('windows.onCreated -- window: ' + createInfo.id); + loadWindowList(); +}); + +chrome.windows.onBoundsChanged.addListener(function (window) { + appendToLog('windows.onBoundsChanged -- window: ' + window.id); + refreshWindow(window.id); +}); + +chrome.windows.onFocusChanged.addListener(function (windowId) { + focusedWindowId = windowId; + appendToLog('windows.onFocusChanged -- window: ' + windowId); + loadWindowList(); +}); + +chrome.windows.onRemoved.addListener(function (windowId) { + appendToLog('windows.onRemoved -- window: ' + windowId); + loadWindowList(); +}); + +chrome.tabs.onCreated.addListener(function (tab) { + appendToLog( + 'tabs.onCreated -- window: ' + + tab.windowId + + ' tab: ' + + tab.id + + ' title: ' + + tab.title + + ' index ' + + tab.index + + ' url ' + + tab.url + ); + loadWindowList(); +}); + +chrome.tabs.onAttached.addListener(function (tabId, props) { + appendToLog( + 'tabs.onAttached -- window: ' + + props.newWindowId + + ' tab: ' + + tabId + + ' index ' + + props.newPosition + ); + loadWindowList(); +}); + +chrome.tabs.onMoved.addListener(function (tabId, props) { + appendToLog( + 'tabs.onMoved -- window: ' + + props.windowId + + ' tab: ' + + tabId + + ' from ' + + props.fromIndex + + ' to ' + + props.toIndex + ); + loadWindowList(); +}); + +async function refreshTab(tabId) { + const tab = await chrome.tabs.get(tabId); + const output = document.getElementById('tab_' + tab.id); + if (!output) return; + renderTab(tab, output); + appendToLog('tab refreshed -- tabId: ' + tab.id + ' url: ' + tab.url); +} + +chrome.tabs.onUpdated.addListener(function (tabId, props) { + appendToLog( + 'tabs.onUpdated -- tab: ' + + tabId + + ' status ' + + props.status + + ' url ' + + props.url + ); + refreshTab(tabId); +}); + +chrome.tabs.onDetached.addListener(function (tabId, props) { + appendToLog( + 'tabs.onDetached -- window: ' + + props.oldWindowId + + ' tab: ' + + tabId + + ' index ' + + props.oldPosition + ); + loadWindowList(); +}); + +chrome.tabs.onActivated.addListener(function (props) { + appendToLog( + 'tabs.onActivated -- window: ' + props.windowId + ' tab: ' + props.tabId + ); + loadWindowList(); +}); + +chrome.tabs.onRemoved.addListener(function (tabId) { + appendToLog('tabs.onRemoved -- tab: ' + tabId); + loadWindowList(); +}); + +async function createWindow() { + const args = { + left: parseInt(document.getElementById('new_window_left').value), + top: parseInt(document.getElementById('new_window_top').value), + width: parseInt(document.getElementById('new_window_width').value), + height: parseInt(document.getElementById('new_window_height').value), + url: document.getElementById('new_window_url').value + }; + + if (!isInt(args.left)) delete args.left; + if (!isInt(args.top)) delete args.top; + if (!isInt(args.width)) delete args.width; + if (!isInt(args.height)) delete args.height; + if (!args.url) delete args.url; + + chrome.windows.create(args).catch(alert); +} + +document + .getElementById('create_window_button') + .addEventListener('click', createWindow); + +async function refreshWindow(windowId) { + const window = await chrome.windows.get(windowId); + const tabList = await chrome.tabs.query({ windowId }); + window.tabs = tabList; + const output = document.getElementById('window_' + window.id); + if (!output) return; + renderWindow(window, output); +} + +function updateWindowData(id) { + const retval = { + left: parseInt(document.getElementById('left_' + id).value), + top: parseInt(document.getElementById('top_' + id).value), + width: parseInt(document.getElementById('width_' + id).value), + height: parseInt(document.getElementById('height_' + id).value) + }; + if (!isInt(retval.left)) delete retval.left; + if (!isInt(retval.top)) delete retval.top; + if (!isInt(retval.width)) delete retval.width; + if (!isInt(retval.height)) delete retval.height; + + return retval; +} + +function updateWindow(id) { + chrome.windows.update(id, updateWindowData(id)).catch(alert); +} + +function removeWindow(windowId) { + chrome.windows + .remove(windowId) + .then(function () { + appendToLog('window: ' + windowId + ' removed.'); + }) + .catch(alert); +} + +async function refreshActiveTab(windowId) { + const tabs = await chrome.tabs.query({ active: true, windowId }); + const output = document.getElementById('tab_' + tabs[0].id); + if (!output) return; + renderTab(tabs[0], output); + appendToLog( + 'Active tab refreshed -- tabId: ' + tabs[0].id + ' url:' + tabs[0].url + ); +} + +document.addEventListener('DOMContentLoaded', function () { + bootstrap(); +}); + +document + .getElementById('load_window_list_button') + .addEventListener('click', function () { + loadWindowList(); + }); +document + .getElementById('update_all_button') + .addEventListener('click', function () { + updateAll(); + }); +document + .getElementById('move_all_button') + .addEventListener('click', function () { + moveAll(); + }); +document + .getElementById('clear_log_button') + .addEventListener('click', function () { + clearLog(); + }); +document + .getElementById('new_window_button') + .addEventListener('click', function () { + chrome.windows.create(); + }); diff --git a/api-samples/tabs/pin/README.md b/api-samples/tabs/pin/README.md new file mode 100644 index 00000000..16989a8c --- /dev/null +++ b/api-samples/tabs/pin/README.md @@ -0,0 +1,19 @@ +# chrome.tabs - Keyboard Pin + +A sample that demonstrates how to use the [`chrome.tabs`](https://developer.chrome.com/docs/extensions/reference/tabs/) API to toggle the pinned state of the current tab. + +## Overview + +In this sample, a new keyboard shortcut (Alt + Shift + P) is enabled to pin the current tab. + +## Implementation Notes + +Chrome fires [`chrome.commands.onCommand`](https://developer.chrome.com/docs/extensions/reference/commands/#event-onCommand) when the user presses a registered keyboard shortcut. + +Calls [`chrome.tabs.update()`](https://developer.chrome.com/docs/extensions/reference/tabs/#method-update) to pin or unpin the current 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. Press Alt + Shift + P to pin the current tab. diff --git a/api-samples/tabs/pin/manifest.json b/api-samples/tabs/pin/manifest.json new file mode 100644 index 00000000..ce600658 --- /dev/null +++ b/api-samples/tabs/pin/manifest.json @@ -0,0 +1,17 @@ +{ + "name": "Keyboard Pin", + "version": "0.3", + "description": "Uses the chrome.tabs API to toggle the pinned state of the current tab.", + "background": { + "service_worker": "service-worker.js" + }, + "commands": { + "toggle-pin": { + "suggested_key": { + "default": "Alt+Shift+P" + }, + "description": "Toggle tab pin" + } + }, + "manifest_version": 3 +} diff --git a/api-samples/tabs/pin/service-worker.js b/api-samples/tabs/pin/service-worker.js new file mode 100644 index 00000000..312cd975 --- /dev/null +++ b/api-samples/tabs/pin/service-worker.js @@ -0,0 +1,9 @@ +chrome.commands.onCommand.addListener(async function (command) { + if (command == 'toggle-pin') { + // Get the currently selected tab + const tabs = await chrome.tabs.query({ active: true, currentWindow: true }); + // Toggle the pinned status + const current = tabs[0]; + chrome.tabs.update(current.id, { pinned: !current.pinned }); + } +}); diff --git a/api-samples/tabs/screenshot/README.md b/api-samples/tabs/screenshot/README.md new file mode 100644 index 00000000..143b4fd5 --- /dev/null +++ b/api-samples/tabs/screenshot/README.md @@ -0,0 +1,17 @@ +# chrome.tabs - Tab Screenshot + +A sample that demonstrates how to use the [`chrome.tabs`](https://developer.chrome.com/docs/extensions/reference/tabs/) API to take a screenshot of the current tab. + +## Overview + +When the user clicks the action icon, the extension takes a screenshot of the current tab and displays it in a new tab. + +## Implementation Notes + +Calls [`chrome.tabs.captureVisibleTab()`](https://developer.chrome.com/docs/extensions/reference/tabs/#method-captureVisibleTab) to capture the visible area of the current 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. Click the extension's icon to take a screenshot of the current tab. diff --git a/api-samples/tabs/screenshot/camera.png b/api-samples/tabs/screenshot/camera.png new file mode 100644 index 00000000..37c5c04b Binary files /dev/null and b/api-samples/tabs/screenshot/camera.png differ diff --git a/api-samples/tabs/screenshot/manifest.json b/api-samples/tabs/screenshot/manifest.json new file mode 100644 index 00000000..e5a44239 --- /dev/null +++ b/api-samples/tabs/screenshot/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "Test Screenshot Extension", + "version": "1.3", + "description": "Uses the chrome.tabs API to take a screenshot of the active tab.", + "background": { + "service_worker": "service-worker.js" + }, + "action": { + "default_icon": "camera.png", + "default_title": "Take a screen shot!" + }, + "permissions": ["activeTab"], + "manifest_version": 3 +} diff --git a/api-samples/tabs/screenshot/screenshot.html b/api-samples/tabs/screenshot/screenshot.html new file mode 100644 index 00000000..48d3a4e4 --- /dev/null +++ b/api-samples/tabs/screenshot/screenshot.html @@ -0,0 +1,20 @@ + + + + Tab Screenshot + + + + Image here: +

+ +

+ + + + 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% +
+
+ +
+
+

+
+
+ Mode:
+ automatic
+ manual
+ disabled +
+
+
+ Scope:
+ per-origin
+ per-tab +
+ +
+

+ +

+ + + 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