├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── background.js ├── clipboard-helper.js ├── icons ├── error.svg └── icon.svg ├── manifest.json └── popup ├── urls-list.css ├── urls-list.html └── urls-list.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | web-ext-artifacts 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Minas Abrahamyan 4 | Copyright (c) 2017 Moritz Heinemann 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Save Tabs URLs with Titles, Restore Later 2 | 3 | Show list the URLs and Titles of all tabs of current browser window as copyable plaintext. 4 | Also can save this info as plaintext and restore it back later to individual tabs. 5 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | browser.contextMenus.create({ 2 | id: "save-tabs-copy-urls", 3 | title: "Copy URLs (all tabs)", 4 | contexts: ["tab"], 5 | }); 6 | browser.contextMenus.onClicked.addListener(function (info, tab) { 7 | if (info.menuItemId === "save-tabs-copy-urls") { 8 | browser.tabs.query({currentWindow: true}).then((tabs) => { 9 | // Use active tab for copy to clipboard, so it is possible to open 10 | // the context menu on inactive tabs. Save clicked tab as fallback. 11 | let activeTabId = tab.id; 12 | let urls = ''; 13 | for (let tab of tabs) { 14 | urls += tab.title + "\r\n" + tab.url + "\r\n\r\n"; 15 | if (tab.active) { 16 | activeTabId = tab.id; 17 | } 18 | } 19 | 20 | // example source: https://github.com/mdn/webextensions-examples/tree/master/context-menu-copy-link-with-types 21 | 22 | // The example will show how data can be copied, but since background 23 | // pages cannot directly write to the clipboard, we will run a content 24 | // script that copies the actual content. 25 | 26 | // clipboard-helper.js defines function copyToClipboard. 27 | const code = "copyToClipboard(" + JSON.stringify(urls) + ");"; 28 | 29 | browser.tabs.executeScript({ 30 | code: "typeof copyToClipboard === 'function';", 31 | }).then(function (results) { 32 | // The content script's last expression will be true if the function 33 | // has been defined. If this is not the case, then we need to run 34 | // clipboard-helper.js to define function copyToClipboard. 35 | if (!results || results[0] !== true) { 36 | return browser.tabs.executeScript(activeTabId, { 37 | file: "clipboard-helper.js", 38 | }); 39 | } 40 | }).then(function () { 41 | return browser.tabs.executeScript(activeTabId, { 42 | code, 43 | }); 44 | }).catch(function (error) { 45 | // This could happen if the extension is not allowed to run code in 46 | // the page, for example if the tab is a privileged page. 47 | console.error("urls-list: Failed to copy text: " + error); 48 | notifyError(); 49 | }); 50 | }); 51 | } 52 | }); 53 | 54 | function notifyError() { 55 | browser.notifications.create({ 56 | "type": "basic", 57 | "iconUrl": browser.extension.getURL("icons/error.svg"), 58 | "title": "Error!", 59 | "message": "Cannot write to clipboard on settings tab!" 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /clipboard-helper.js: -------------------------------------------------------------------------------- 1 | // example source: https://github.com/mdn/webextensions-examples/tree/master/context-menu-copy-link-with-types 2 | 3 | // This function must be called in a visible page, such as a browserAction popup 4 | // or a content script. Calling it in a background page has no effect! 5 | function copyToClipboard(text) { 6 | function oncopy(event) { 7 | document.removeEventListener("copy", oncopy, true); 8 | // Hide the event from the page to prevent tampering. 9 | event.stopImmediatePropagation(); 10 | 11 | // Overwrite the clipboard content. 12 | event.preventDefault(); 13 | event.clipboardData.setData("text/plain", text); 14 | } 15 | 16 | document.addEventListener("copy", oncopy, true); 17 | 18 | // Requires the clipboardWrite permission, or a user gesture: 19 | document.execCommand("copy"); 20 | } 21 | -------------------------------------------------------------------------------- /icons/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Save Tab URLs with Titles, Restore Later", 4 | "version": "0.1.1", 5 | "description": "Show, save and restore the URLs with Titles of all tabs of current browser window. Show as copyable plaintext. Save and restore from text file.", 6 | "homepage_url": "https://github.com/mnba/save-tabs", 7 | "permissions": [ 8 | "activeTab", 9 | "clipboardWrite", 10 | "contextMenus", 11 | "notifications", 12 | "tabs" 13 | ], 14 | "background": { 15 | "scripts": [ 16 | "background.js" 17 | ] 18 | }, 19 | "browser_action": { 20 | "default_icon": "icons/icon.svg", 21 | "browser_style": true, 22 | "default_title": "URLs List", 23 | "default_popup": "popup/urls-list.html" 24 | }, 25 | "icons": { 26 | "48": "icons/icon.svg", 27 | "96": "icons/icon.svg" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /popup/urls-list.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 350px; 3 | } 4 | 5 | .panel { 6 | width: 100%; 7 | } 8 | 9 | .filterBox { 10 | padding-left: 6px; 11 | padding-top: 10px; 12 | } 13 | 14 | .sortButtons { 15 | float:right; 16 | padding-right: 6px; 17 | } 18 | 19 | .filterInput { 20 | width: 150px; 21 | } 22 | 23 | .filterWarning { 24 | background-color: orange; 25 | text-align: center; 26 | padding-top: 3px; 27 | padding-bottom: 3px; 28 | display: none; 29 | } 30 | 31 | .urlText { 32 | height: 400px; 33 | margin-top: -1px; 34 | resize: none; 35 | white-space: pre; 36 | width: 100%; 37 | } 38 | 39 | .buttonRow { 40 | padding: 5px; 41 | text-align: center; 42 | width: 100%; 43 | } 44 | 45 | .buttonRow > button { 46 | margin: 5px; 47 | } 48 | 49 | .hide { 50 | display: none; 51 | } 52 | -------------------------------------------------------------------------------- /popup/urls-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 |