├── .eslintrc.yml
├── .gitignore
├── Chrome
├── _locales
│ ├── de
│ │ └── messages.json
│ └── en
│ │ └── messages.json
├── eventPage.js
├── hibernationPage
│ └── index.html
├── icon128.png
├── icon19.png
├── icon38.png
├── manifest.json
├── options.html
└── options.js
├── LICENSE
├── Makefile
├── README.md
├── TabHibernation.safariextension
├── Icon-32.png
├── Info.plist
├── global-page.html
├── global-script.js
└── hibernationPage
│ └── index.html
├── lib
├── icon16.png
└── icon48.png
└── material
├── icon128.pxm
├── icon16.pxm
├── icon19.pxm
├── icon38.pxm
├── icon48.pxm
├── screenshot1280.pxm
├── screenshot1_chrome.png
├── screenshot2_chrome.png
├── screenshot3_chrome.png
├── small_tile_chrome.png
└── small_tile_chrome.psd
/.eslintrc.yml:
--------------------------------------------------------------------------------
1 | extends: eslint:recommended
2 |
3 | env:
4 | es6: true,
5 | browser: true,
6 | webextensions: true
7 |
8 | rules:
9 | # warnings
10 | no-control-regex: warn
11 | no-ternary: warn
12 | prefer-const: warn
13 | prefer-template: warn
14 | no-magic-numbers:
15 | - warn
16 | - ignoreArrayIndexes: true
17 | # errors
18 | yoda: error
19 | radix: error
20 | no-useless-return: error
21 | no-useless-concat: error
22 | no-unused-expressions: error
23 | no-self-compare: error
24 | no-script-url: error
25 | no-return-assign: error
26 | no-octal-escape: error
27 | no-loop-func: error
28 | no-lone-blocks: error
29 | no-invalid-this: error
30 | no-implied-eval: error
31 | no-floating-decimal: error
32 | no-eval: error
33 | no-eq-null: error
34 | no-empty-function: error
35 | no-else-return: error
36 | no-alert: error
37 | eqeqeq: error
38 | dot-notation: error
39 | no-shadow: error
40 | no-undef-init: error
41 | no-use-before-define: error
42 | brace-style: error
43 | comma-dangle: error
44 | comma-spacing: error
45 | eol-last: error
46 | func-call-spacing: error
47 | keyword-spacing: error
48 | linebreak-style: error
49 | no-bitwise: error
50 | no-lonely-if: error
51 | no-multi-assign: error
52 | no-multiple-empty-lines: error
53 | no-nested-ternary: error
54 | no-trailing-spaces: error
55 | semi-spacing: error
56 | space-before-blocks: error
57 | space-in-parens: error
58 | space-infix-ops: error
59 | switch-colon-spacing: error
60 | wrap-regex: error
61 | no-confusing-arrow: error
62 | no-useless-computed-key: error
63 | no-var: error
64 | template-curly-spacing: error
65 | semi:
66 | - error
67 | - never
68 | no-extra-parens:
69 | - error
70 | - all
71 | valid-typeof:
72 | - error
73 | - requireStringLiterals: true
74 | strict:
75 | - error
76 | - global
77 | curly:
78 | - error
79 | - multi-line
80 | indent:
81 | - error
82 | - tab
83 | quotes:
84 | - error
85 | - single
86 | space-before-function-paren:
87 | - error
88 | - never
89 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/TabHibernation.iml
2 | .idea/scopes/scope_settings.xml
3 | .idea/codeStyleSettings.xml
4 |
5 | Chrome/icon16.png
6 | Chrome/icon48.png
7 | TabHibernation.safariextension/icon16.png
8 | TabHibernation.safariextension/Icon-48.png
9 |
10 | node_modules/
11 |
--------------------------------------------------------------------------------
/Chrome/_locales/de/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extensionName": {
3 | "message": "Tab Hibernation",
4 | "description": "Name der Extension"
5 | },
6 | "extensionDescription": {
7 | "message": "schickt inaktive Tabs schlafen",
8 | "description": "Beschreibung der Extension"
9 | },
10 | "defaultTitle": {
11 | "message": "inaktive Tabs schlafen legen",
12 | "description": "Tooltip, der in der Google Chrome Toolbar erscheint"
13 | },
14 | "commandsDescription": {
15 | "message": "Aktiven Tab schlafen legen",
16 | "description": "Beschreibung für den Keyboard-Shortcut"
17 | },
18 | "contextMenuTitle": {
19 | "message": "Diese Seite schlafen legen",
20 | "description": "Titel des Kontextmenüeintrags"
21 | },
22 | "saveOptions": {
23 | "message": "Optionen gespeichert.",
24 | "description": "Statustext, wenn Optionen gespeichert werden"
25 | },
26 | "optionsTitle": {
27 | "message": "Tab Hibernation - Einstellungen",
28 | "description": "Titel der Einstellungsseite"
29 | },
30 | "optionsHeading": {
31 | "message": "Whitelist",
32 | "description": "Überschrift der Einstellungsseite"
33 | },
34 | "optionsFirstParagraph": {
35 | "message": "Hier können URLs eingetragen werden, die nicht schlafen gelegt werden sollen, wenn der \"inaktive Tabs schlafen legen\"-Button gedrückt wird.",
36 | "description": "Erster Absatz der Einstellungsseite"
37 | },
38 | "optionsSecondParagraph": {
39 | "message": "Wenn z.B. \"sub.example.com\" und \"test.example.com\" wach gehalten werden sollen, müssen beide eingetragen werden und nicht nur \"*.example.com\"",
40 | "description": "Zweiter Absatz der Einstellungsseite"
41 | },
42 | "optionsThirdParagraph": {
43 | "message": "Hier kann auch ein Pfad wie \"example.com/path/\" eingetragen werden, wenn nicht die gesamte Domain wach gehalten werden soll.",
44 | "description": "Dritter Absatz der Einstellungsseite"
45 | },
46 | "optionsFirstStrong": {
47 | "message": "URLs müssen vollständig sein und http/https enthalten!",
48 | "description": "Erster fett formatierter Satz auf der Einstellungsseite"
49 | },
50 | "optionsSecondStrong": {
51 | "message": "Eine URL pro Zeile!",
52 | "description": "Zweiter fett formatierter Satz auf der Einstellungsseite"
53 | },
54 | "optionsAudible": {
55 | "message": "Tabs die Audio spielen wach halten",
56 | "description": "Checkbox für die Audio-Einstellung"
57 | },
58 | "optionsThumbnails": {
59 | "message": "Screenshot von manuell schlafen gelegten Tabs anzeigen (experimentell!)",
60 | "description": "Checkbox für die Thumbnail-Einstellung"
61 | },
62 | "optionsSaveButton": {
63 | "message": "Speichern",
64 | "description": "Text des Speichern-Buttons"
65 | },
66 | "hibernationPageInfo": {
67 | "message": "schläft",
68 | "description": "Infotext, der auf der inaktiven Seite angezeigt wird"
69 | },
70 | "hibernationPageButton": {
71 | "message": "Aufwecken",
72 | "description": "Text des Aufweck-Buttons einer inaktiven Seite"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Chrome/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "extensionName": {
3 | "message": "Tab Hibernation",
4 | "description": "extension name"
5 | },
6 | "extensionDescription": {
7 | "message": "Sends your inactive tabs to sleep",
8 | "description": "extension description"
9 | },
10 | "defaultTitle": {
11 | "message": "send inactive tabs to sleep",
12 | "description": "tooltip shown in the main Google Chrome toolbar"
13 | },
14 | "commandsDescription": {
15 | "message": "Hibernate the current Tab",
16 | "description": "keyboard shortcut description"
17 | },
18 | "contextMenuTitle": {
19 | "message": "Hibernate This Page",
20 | "description": "context menu entry title"
21 | },
22 | "saveOptions": {
23 | "message": "Options saved.",
24 | "description": "status text when options are saved"
25 | },
26 | "optionsTitle": {
27 | "message": "Tab Hibernation - Settings",
28 | "description": "options page title"
29 | },
30 | "optionsHeading": {
31 | "message": "Whitelist",
32 | "description": "options page heading"
33 | },
34 | "optionsFirstParagraph": {
35 | "message": "Here you can input a whitelist for URLs which should not be hibernated when pressing the \"send inactive tabs to sleep\"-Button.",
36 | "description": "options page first paragraph"
37 | },
38 | "optionsSecondParagraph": {
39 | "message": "For example: if you want to whitelist \"sub.example.com\" and \"test.example.com\", you MUST put them both and can't just type \"*.example.com\"",
40 | "description": "options page second paragraph"
41 | },
42 | "optionsThirdParagraph": {
43 | "message": "You can also put something like \"example.com/path/\" in here, if you don't want to whitelist the whole domain.",
44 | "description": "options page third paragraph"
45 | },
46 | "optionsFirstStrong": {
47 | "message": "URLs must be complete and include http/https!",
48 | "description": "options page first bold sentence"
49 | },
50 | "optionsSecondStrong": {
51 | "message": "One URL per line!",
52 | "description": "options page second bold sentence"
53 | },
54 | "optionsAudible": {
55 | "message": "keep audio-playing tabs awake",
56 | "description": "checkbox for the audio-option"
57 | },
58 | "optionsThumbnails": {
59 | "message": "show screenshot of manually hibernated tabs (experimental!)",
60 | "description": "checkbox for the thumbnail-option"
61 | },
62 | "optionsSaveButton": {
63 | "message": "Save",
64 | "description": "save button text"
65 | },
66 | "hibernationPageInfo": {
67 | "message": "is hibernating",
68 | "description": "info shown when page is hibernating"
69 | },
70 | "hibernationPageButton": {
71 | "message": "Wake up!",
72 | "description": "text of the button to wake up a hibernating page"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Chrome/eventPage.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | let whitelistArray = new Array()
4 | let caffeinatedAudio = false
5 | let thumbnails = false
6 |
7 | chrome.storage.sync.get(function(items) {
8 | if (items.whitelist) whitelistArray = items.whitelist.split('\n')
9 | if (items.audible !== undefined) caffeinatedAudio = items.audible
10 | if (items.thumbnails !== undefined) thumbnails = items.thumbnails
11 | })
12 |
13 | chrome.storage.onChanged.addListener(function(changes, area) {
14 | if (area === 'sync') {
15 | chrome.storage.sync.get(function(items) {
16 | if (items.whitelist) whitelistArray = items.whitelist.split('\n')
17 | if (items.audible !== undefined) caffeinatedAudio = items.audible
18 | if (items.thumbnails !== undefined) thumbnails = items.thumbnails
19 | })
20 | }
21 | })
22 |
23 | chrome.runtime.onInstalled.addListener(function() {
24 | chrome.contextMenus.create({
25 | id : 'SleepTab',
26 | title : chrome.i18n.getMessage('contextMenuTitle'),
27 | contexts : ['page']
28 | })
29 | })
30 |
31 | function inWhitelist(url) {
32 | whitelistArray.forEach(function(item) {
33 | if (url.startsWith(item)) {
34 | return true
35 | }
36 | })
37 | return false
38 | }
39 |
40 | function sleepTab(html, tab, img) {
41 | const pageInfo = {
42 | url: tab.url,
43 | title: tab.title,
44 | hibernationInfo: chrome.i18n.getMessage('hibernationPageInfo'),
45 | buttonText: chrome.i18n.getMessage('hibernationPageButton'),
46 | favIconUrl: tab.favIconUrl
47 | }
48 |
49 | const pageHtml = html.replace(/\{\/\*pageInfoObject\*\/\}/, JSON.stringify(pageInfo))
50 |
51 | if (img && thumbnails) {
52 | const canvas = document.createElement('canvas')
53 | const ctx = canvas.getContext('2d')
54 |
55 | const i = new Image()
56 | i.src = img
57 | i.onload = function() {
58 | canvas.width = 300
59 | canvas.height = canvas.width * (i.height / i.width)
60 | ctx.drawImage(i, 0, 0, canvas.width, canvas.height)
61 | img = ``
62 |
63 | const dataURL = `data:text/html;charset=UTF-8,${encodeURIComponent(pageHtml.replace(//, img))}`
64 | chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true})
65 | }
66 | return
67 | }
68 | const dataURL = `data:text/html;charset=UTF-8,${encodeURIComponent(pageHtml)}`
69 | chrome.tabs.update(tab.id, {url: dataURL, autoDiscardable: true})
70 | }
71 |
72 | function sleepSingle(tab) {
73 | const xmlHttp = new XMLHttpRequest()
74 | xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true)
75 | xmlHttp.onload = function() {
76 | chrome.tabs.captureVisibleTab({format: 'png'}, function(img) {
77 | sleepTab(xmlHttp.responseText, tab, img)
78 | })
79 | }
80 | xmlHttp.send(null)
81 | }
82 |
83 | chrome.browserAction.onClicked.addListener(function() {
84 | const xmlHttp = new XMLHttpRequest()
85 | xmlHttp.open('GET', chrome.runtime.getURL('hibernationPage/index.html'), true)
86 | xmlHttp.onload = function() {
87 | chrome.tabs.query({
88 | active: false,
89 | pinned: false,
90 | highlighted: false,
91 | status: 'complete',
92 | url: ['http://*/*', 'https://*/*', 'ftp://*/*', 'file://*/*']
93 | }, function(tabs) {
94 | tabs.forEach(function(tab) {
95 | if (inWhitelist(tab.url)) return
96 | if (tab.audible && caffeinatedAudio) return
97 | sleepTab(xmlHttp.responseText, tab)
98 | })
99 | })
100 | }
101 | xmlHttp.send(null)
102 | })
103 |
104 | chrome.contextMenus.onClicked.addListener(function(info, tab) {
105 | sleepSingle(tab)
106 | })
107 |
108 | chrome.commands.onCommand.addListener(function() {
109 | chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) {
110 | sleepSingle(tabs[0])
111 | })
112 | })
113 |
--------------------------------------------------------------------------------
/Chrome/hibernationPage/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 |
24 | 25 | 26 |
27 | 28 |29 | 30 | 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Chrome/options.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function save_options() { 4 | const whitelist = document.getElementById('whitelist').value 5 | const audible = document.getElementById('audible').checked 6 | const thumbnails = document.getElementById('thumbnails').checked 7 | 8 | chrome.storage.sync.set({whitelist: whitelist, audible: audible, thumbnails: thumbnails}, function() { 9 | const status = document.getElementById('status') 10 | status.textContent = chrome.i18n.getMessage('saveOptions') 11 | setTimeout(function() { 12 | status.textContent = '' 13 | }, 750) 14 | }) 15 | } 16 | 17 | function restore_options() { 18 | chrome.storage.sync.get(function(items) { 19 | document.getElementById('whitelist').value = items.whitelist 20 | document.getElementById('audible').checked = items.audible 21 | document.getElementById('thumbnails').checked = items.thumbnails 22 | }) 23 | } 24 | 25 | document.addEventListener('DOMContentLoaded', restore_options) 26 | document.getElementById('title').innerText = chrome.i18n.getMessage('optionsTitle') 27 | document.getElementById('heading').innerText = chrome.i18n.getMessage('optionsHeading') 28 | document.getElementById('firstParagraph').innerText = chrome.i18n.getMessage('optionsFirstParagraph') 29 | document.getElementById('secondParagraph').innerText = chrome.i18n.getMessage('optionsSecondParagraph') 30 | document.getElementById('thirdParagraph').innerText = chrome.i18n.getMessage('optionsThirdParagraph') 31 | document.getElementById('firstStrong').innerText = chrome.i18n.getMessage('optionsFirstStrong') 32 | document.getElementById('secondStrong').innerText = chrome.i18n.getMessage('optionsSecondStrong') 33 | document.getElementById('audibleSpan').innerText = chrome.i18n.getMessage('optionsAudible') 34 | document.getElementById('thumbnailsSpan').innerText = chrome.i18n.getMessage('optionsThumbnails') 35 | document.getElementById('save').innerText = chrome.i18n.getMessage('optionsSaveButton') 36 | document.getElementById('save').addEventListener('click', save_options) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Max Winde 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @ln -f lib/icon16.png Chrome/icon16.png 3 | @ln -f lib/icon16.png TabHibernation.safariextension/icon16.png 4 | @ln -f lib/icon48.png Chrome/icon48.png 5 | @ln -f lib/icon48.png TabHibernation.safariextension/Icon-48.png 6 | 7 | clean: 8 | @rm -rf Chrome/icon16.png 9 | @rm -rf TabHibernation.safariextension/icon16.png 10 | @rm -rf Chrome/icon48.png 11 | @rm -rf TabHibernation.safariextension/Icon-48.png 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TabHibernate 2 | TabHibernate is a Browser Extension that will send inactive Tabs into some kind of "Hibernate" mode – Mostly like [OneTab](https://chrome.google.com/webstore/detail/onetab/chphlpgkkbolifaimnlloiipkdnihall) but without removing the tabs from the tabbar. 3 | 4 | You may download the extension in the [Chrome Web Store](https://chrome.google.com/webstore/detail/tab-hibernation/ddklinnhageaolpojclheieamdiphabe). 5 | 6 | **Currently only the Chrome-Version is really supported. 7 | Safari-Version needs a maintainer and a Firefox-Version would be nice to have, contact [@HorayNarea](https://github.com/HorayNarea) if you want to do something about that.** 8 | 9 | ## Building the extension 10 | Code (or other files) that can be used by both the Chrome and Safari extension are stored in `lib/`. Browser-specific code is stored in `Chrome/` for Chrome and in `TabHibernation.safariextension/` for Safari. 11 | 12 | To make the files in `lib/` accessible from both extensions hard links need to be created. Just run `make` to create these links. 13 | 14 | ## Screenshot 15 |  16 | -------------------------------------------------------------------------------- /TabHibernation.safariextension/Icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/343max/TabHibernation/43f58eef47ea0cd0ca2a2ff55a61f44d2a5df377/TabHibernation.safariextension/Icon-32.png -------------------------------------------------------------------------------- /TabHibernation.safariextension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |