From bbcd02585111a166cb376df91c9259ca43d35872 Mon Sep 17 00:00:00 2001 From: Xuezhou Dai Date: Mon, 13 Nov 2023 21:35:41 +0800 Subject: [PATCH] Migration tabs sample (#964) * Add pin sample * Add README.md * Add screenshot sample * Add README.md * Add zoom sample * Add README.md * Add inspector sample * Add README.md * Fix typo * Remove wrapper * Remove inline style * Update api-samples/tabs/pin/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/screenshot/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/zoom/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/zoom/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/zoom/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/zoom/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/inspector/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/zoom/README.md Co-authored-by: Joe Medley * Update README * Update for loop * Remove children select * Change selected to active * Fix event listener * Set checkboxs to disabled * Fix layout * Fix wrong input data in create tab function * Update api-samples/tabs/zoom/README.md Co-authored-by: Joe Medley * Update description * Update description * Update description * Fix typo * Update description * Rename * Fix file name * Update active check box * Update code style * Update api-samples/tabs/zoom/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/pin/README.md Co-authored-by: Joe Medley * Update api-samples/tabs/pin/README.md Co-authored-by: Joe Medley * Apply suggestions from code review Co-authored-by: Joe Medley * Update api-samples/tabs/inspector/manifest.json Co-authored-by: Joe Medley * Fix case issues * Update api-samples/tabs/inspector/manifest.json --------- Co-authored-by: Joe Medley Co-authored-by: Oliver Dunk --- api-samples/tabs/inspector/README.md | 13 + api-samples/tabs/inspector/manifest.json | 13 + api-samples/tabs/inspector/service-worker.js | 5 + .../inspector/window_and_tabs_manager.css | 110 +++++ .../inspector/window_and_tabs_manager.html | 107 +++++ .../tabs/inspector/window_and_tabs_manager.js | 437 ++++++++++++++++++ api-samples/tabs/pin/README.md | 19 + api-samples/tabs/pin/manifest.json | 17 + api-samples/tabs/pin/service-worker.js | 9 + api-samples/tabs/screenshot/README.md | 17 + api-samples/tabs/screenshot/camera.png | Bin 0 -> 1087 bytes api-samples/tabs/screenshot/manifest.json | 14 + api-samples/tabs/screenshot/screenshot.html | 20 + api-samples/tabs/screenshot/screenshot.js | 9 + api-samples/tabs/screenshot/service-worker.js | 24 + api-samples/tabs/screenshot/white.png | Bin 0 -> 132 bytes api-samples/tabs/zoom/README.md | 21 + api-samples/tabs/zoom/manifest.json | 18 + api-samples/tabs/zoom/popup.html | 78 ++++ api-samples/tabs/zoom/popup.js | 124 +++++ api-samples/tabs/zoom/service-worker.js | 26 ++ api-samples/tabs/zoom/zoom16.png | Bin 0 -> 315 bytes api-samples/tabs/zoom/zoom19.png | Bin 0 -> 421 bytes api-samples/tabs/zoom/zoom48.png | Bin 0 -> 1429 bytes 24 files changed, 1081 insertions(+) create mode 100644 api-samples/tabs/inspector/README.md create mode 100644 api-samples/tabs/inspector/manifest.json create mode 100644 api-samples/tabs/inspector/service-worker.js create mode 100644 api-samples/tabs/inspector/window_and_tabs_manager.css create mode 100644 api-samples/tabs/inspector/window_and_tabs_manager.html create mode 100644 api-samples/tabs/inspector/window_and_tabs_manager.js create mode 100644 api-samples/tabs/pin/README.md create mode 100644 api-samples/tabs/pin/manifest.json create mode 100644 api-samples/tabs/pin/service-worker.js create mode 100644 api-samples/tabs/screenshot/README.md create mode 100644 api-samples/tabs/screenshot/camera.png create mode 100644 api-samples/tabs/screenshot/manifest.json create mode 100644 api-samples/tabs/screenshot/screenshot.html create mode 100644 api-samples/tabs/screenshot/screenshot.js create mode 100644 api-samples/tabs/screenshot/service-worker.js create mode 100644 api-samples/tabs/screenshot/white.png create mode 100644 api-samples/tabs/zoom/README.md create mode 100644 api-samples/tabs/zoom/manifest.json create mode 100644 api-samples/tabs/zoom/popup.html create mode 100644 api-samples/tabs/zoom/popup.js create mode 100644 api-samples/tabs/zoom/service-worker.js create mode 100644 api-samples/tabs/zoom/zoom16.png create mode 100644 api-samples/tabs/zoom/zoom19.png create mode 100644 api-samples/tabs/zoom/zoom48.png 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 0000000000000000000000000000000000000000..37c5c04be0dc1c35b04ac4779489e4c1b9cc8bb3 GIT binary patch literal 1087 zcmV-F1i<@=P)>yyJ(rlP+tJ`?9NM=chU(a7iX^YZeJ1p-B_?pO{6*Q3$jR#yXofYU~fsw319FpFU0C8H>gLKWKAv^Ve|L?e_d& zvpb#Bnm(;iC_DkE!DE47Yin!3<)6;Kdi9!CtLy3MQLEKTr4mKq z96O0ceJLol%9wpsRTb@gv7)JEbabpGWv$K4T8)OqWWJS?!{KmrIvt3IK|r88#4~3q z1p=({Hzw22(2%B2x3F*@rNPDGT8GW<^>`kwtiUZJBXl|)Az&Q<0pHQlvAeq)kHsE^ zLcyh_hs(8q(e(E2gLph1lzbR8I56<@;$k=)#`=XqA>3K%9M#jOrjgkJsz4*$4yzrg;DaDdLnyqsbfOSeAWIeSLjl;kn}C3oe(- zc*j^#aYfPCgjV8$dv30%s0dL + + + 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 0000000000000000000000000000000000000000..52b9a9f121f47379586e8de200e0877a075465c7 GIT binary patch literal 132 zcmeAS@N?(olHy`uVBq!ia0y~yU}|7sV0^&H3>4v)_gfC6m;-!5T>t<7zx;^w2_V-fwQfK0-mKx6s?2F9Z|{e?klJzf1=);T3K0RTvQ8vp + + + 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 0000000000000000000000000000000000000000..d1bdbddf402dfed428723fb01fab7ffb8d4100be GIT binary patch literal 315 zcmV-B0mS}^P)^U4Ej zj1v<$pq~fIw0Po~Eyjqc8hgC4nSd%y?&-151R)Q0d0`_1fmyEk=9G}X9TT1+rm;HV zi#t~G^KB+D!!nHw%<)c-FZ%fg>EqfA*vUYHE*;JaT@+V?(U&hWQhyJ>5PTYjG$#N6 N002ovPDHLkV1gtNgN*^wF~`kbYnPf3@ODkw!} zpOZ z$^{Azg*j$jW)`$Kk{sk1x0$fU)zCUL!@!&j&dVE^=2GnlMc@`3!w#97_!YB zdUW%p&6(4Ntj!9K_|AQ5TI5sRFh{IW|DD<5B=;ii2`jW^V46LKyr7b1o8#PK%4;sq zs#}Lstn!e)Fx!+|V}o_B=O%OEF>`Vv_|w}Cmw3iE?sA@r0n5pNGABP1l)_x(LDpq6 z3zTK@g|}q;yyMkB?fc4@$*%TpF=8pqMLzIGnk5zWe5yHjW&ihc|Nn&F2|9w=ao`ky%MNsgQpZyX2iGK2vh=?fl*7Ra}No$*GlDcY| zcDI}C8R3EV!?3WIB!WVRmti){ne)thoii!sFJ9!o)M~*03g{V7nH*Hmg#-n=Z7DXY zVRK={CZctfB{@L^g~Dti>Iz=VSh zmR^L!3sLsVbb2mvuUMt zY`_Ci+RhuM6W&(z_z(!ZG?*axq`VF!~s zX+xk@_zAAK!xX_gbPJ%%nzMwY^lvSJ7T_bOM=sHUXz8Kfz*YDQQiNDF5>QgTtAi4w z#&v=!c_|byX1@gw4dW$PRy|eFjMT~~Y37f<>g_6}D6he3v)9WoC(|El<$I0QgldO5 zHlnPd^eF{ECC!vH@4y+O_eJg1fpxeBm*BpB9g3kC6=?XBQ#0!y2=$zVL(=>W_f*eK zc;L^JqwGCUCD1tRH?oey33ye$7OaG&mf;a>MFD5efl_{Mni1l(K=aaU={k4J8cVQd z4l6>)AMZshMn9m*6yS5##Sb)=y5V=YEy@k8wkdZ#rJgq#nD%72jt2sz5XY`1&4TJV zV`KqMZFEhV70k={f})oPBBzrrt_%c{I_eIff!>DmnrvFcHmqx@AI+$n(oj_cOHG1G z>1IdAzCM)lOjsILAHpd(=qitNoqKRaAa!H1t_^l&fh@rGT_Pm=`%o1qh84%)xPuuN zS~J40YqC}Os-eRwAx#c6ILa)-95B33D|0Xbb%E}9u)3;K7tk0X<%D{aj^k>$DD%{& znJ%cIX?L>;>#FC6D4!*9k{<=Aqo}05PF)F}eEnfKD$3EwIxSHyiLwTdT@ENl$;*XO zBcy-WYhVhEby7>cZYkY|=XMqIg+MJRd(M_ArNJdA53#D(hkAX=V=t$om`_JWUXG01 z3@^^oni*&y&11N)zNd_a-=Gb(3@B1geBCjpV%}0o^H6h%MdgmQ^C{GK^hsG52Vbe< zhU=_DGp~JRzpvo_r)h{+r9yF_NDzmMYT=!hepKYgyA->!Y)WwSNb;(_N>8o+WKi^PB+E^yl0V~5alL(t5+l` z0fC(HzP|i|C>@z5VL_&ZGRn98FIxPH9HofvhePnOOf!1-mvBq0E&-&m4R!TYBb+~+ zKpEk(X54-Qo{M#eK@#_%b^uT4&ad)sdkS5BKg>qW%MURy>K#p9#(R$+F-OYY^H62g zT=j?F_Pc5muCr_kg-D?>*o3<<7uPW>(28wmpVEs^IcZywdWTJqp$Ru!b3A~?BI$pw zv&V%M>v>u9;vC95hanSVWa2Lv&k>4w jvr*Qb{jcL0&~Dp5+ARQLopT4t00000NkvXXu0mjfo;kPV literal 0 HcmV?d00001