├── .gitignore ├── icon128.png ├── icon16.png ├── icon19.png ├── icon38.png ├── icon48.png ├── promotional_440x280.png ├── screenshot_440x280_1.png ├── screenshot_440x280_2.png ├── screenshot_640x400_1.png ├── screenshot_640x400_2.png ├── make_zip.sh ├── manifest.json ├── LICENSE ├── README.md ├── popup.html ├── popup.js ├── icon.svg ├── background.js └── promotional_440x280.svg /.gitignore: -------------------------------------------------------------------------------- 1 | crx-reload-tab.zip 2 | -------------------------------------------------------------------------------- /icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/icon128.png -------------------------------------------------------------------------------- /icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/icon16.png -------------------------------------------------------------------------------- /icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/icon19.png -------------------------------------------------------------------------------- /icon38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/icon38.png -------------------------------------------------------------------------------- /icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/icon48.png -------------------------------------------------------------------------------- /promotional_440x280.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/promotional_440x280.png -------------------------------------------------------------------------------- /screenshot_440x280_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/screenshot_440x280_1.png -------------------------------------------------------------------------------- /screenshot_440x280_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/screenshot_440x280_2.png -------------------------------------------------------------------------------- /screenshot_640x400_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/screenshot_640x400_1.png -------------------------------------------------------------------------------- /screenshot_640x400_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/denilsonsa/crx-reload-tab/HEAD/screenshot_640x400_2.png -------------------------------------------------------------------------------- /make_zip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f crx-reload-tab.zip 4 | zip -9Xr crx-reload-tab.zip \ 5 | background.js \ 6 | icon128.png \ 7 | icon16.png \ 8 | icon19.png \ 9 | icon38.png \ 10 | icon48.png \ 11 | manifest.json \ 12 | popup.html \ 13 | popup.js 14 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "Tab auto reloader", 5 | "short_name": "Tab reloader", 6 | "description": "This extension reloads the current tab at a chosen interval.", 7 | "version": "1.1", 8 | "homepage_url": "https://github.com/denilsonsa/crx-reload-tab/", 9 | 10 | "icons": { 11 | "16": "icon16.png", 12 | "48": "icon48.png", 13 | "128": "icon128.png" 14 | }, 15 | "browser_action": { 16 | "default_icon": { 17 | "19": "icon19.png", 18 | "38": "icon38.png" 19 | }, 20 | "default_popup": "popup.html", 21 | "default_title": "Reload this page every…" 22 | }, 23 | "background": { 24 | "scripts": [ "background.js" ], 25 | "persistent": true 26 | }, 27 | "permissions": [ 28 | "contextMenus", 29 | "storage" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Denilson Figueiredo de Sá 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tab auto reloader (Chrome extension) 2 | ==================================== 3 | 4 | [Donation - buy me a coffee](https://denilson.sa.nom.br/donate.html) 5 | 6 | Chrome extension to reload the current tab at a chosen interval. [Get it on Chrome Web Store!][cws] 7 | 8 | [![Screenshot of this extension](./screenshot_440x280_2.png)][cws] 9 | 10 | Features: 11 | 12 | * Free and open-source. 13 | * No (extra) permissions required. 14 | * Easy-to-use. 15 | * The reload interval can be different for each tab. 16 | 17 | The icon is a remix of public-domain cliparts [stopwatch by 18 | markroth8][stopwatch] and [Reload icon by mlampret][reload]. 19 | 20 | Technical stuff 21 | --------------- 22 | 23 | ### Persistent background page 24 | 25 | This extension is implemented using a persistent background page. Ideally, it 26 | could have been an [event page][event_pages] that only gets loaded when there 27 | is a tab being reloaded, and gets unloaded if no tabs are being auto-reloaded. 28 | 29 | However, I could not find a way to keep an event page active. The documentation 30 | states that *"The event page will not shut down until all message ports are 31 | closed."*, but keeping a global [`Port` object][port] at the background page 32 | was not enough to prevent it from being suspended. 33 | 34 | There is also the [alarms API][alarms], but the minimum interval is one minute, 35 | which is unsuitable for the purposes of this extension. 36 | 37 | If someone finds a better way to implement this extension, feel free to open 38 | [an issue][issues] or send [a pull request][pulls]. 39 | 40 | ### Icon badge and Chrome event listeners 41 | 42 | The small text below the icon is called [badge][]. When the [badge text is 43 | set][setBadgeText] to a single tab, the badge gets reset whenever the tab loads 44 | another page (or reloads the same page). 45 | 46 | For that reason, the extension adds a listener to 47 | [`chrome.tabs.onUpdated`][onUpdated] to restore the badge text after reloading. 48 | 49 | In addition, the extension adds a listener to 50 | [`chrome.tabs.onRemoved`][onRemoved] to clear the reload whenever a tab gets 51 | closed. 52 | 53 | The extension is smart enough to remove the listeners if no tab is being 54 | auto-reloaded. 55 | 56 | 57 | [cws]: https://chrome.google.com/webstore/detail/knnahnemielbnanghaphjgheamgcjjcb 58 | [issues]: https://github.com/denilsonsa/crx-reload-tab/issues 59 | [pulls]: https://github.com/denilsonsa/crx-reload-tab/pulls 60 | [reload]: https://openclipart.org/detail/171074/reload-icon 61 | [stopwatch]: https://openclipart.org/detail/173421/stopwatch 62 | [alarms]: https://developer.chrome.com/extensions/alarms 63 | [event_pages]: https://developer.chrome.com/extensions/event_pages 64 | [port]: https://developer.chrome.com/extensions/runtime#type-Port 65 | [badge]: https://developer.chrome.com/extensions/browserAction#badge 66 | [setBadgeText]: https://developer.chrome.com/extensions/browserAction#method-setBadgeText 67 | [onUpdated]: https://developer.chrome.com/extensions/tabs#event-onUpdated 68 | [onRemoved]: https://developer.chrome.com/extensions/tabs#event-onRemoved 69 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Tab auto reloader 7 | 50 | 51 | 52 | 53 | 56 | 57 |
58 |
59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 |
68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
85 |
86 | 87 |
88 |
89 | days, 90 | hours, 91 | minutes, 92 | seconds 93 | 94 |
95 |
96 | 97 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | ////////////////////////////////////////////////////////////////////// 4 | // Convenience wrapper function. 5 | 6 | function run_with_tabId_and_bgPage(callback) { 7 | chrome.tabs.query({currentWindow: true, active : true}, function(tabs) { 8 | chrome.runtime.getBackgroundPage(function(bgpage) { 9 | callback(tabs[0].id, bgpage); 10 | }); 11 | }); 12 | } 13 | 14 | ////////////////////////////////////////////////////////////////////// 15 | // Button handlers. 16 | 17 | function preset_button_click_handler(ev) { 18 | run_with_tabId_and_bgPage(function(tab_id, bgpage) { 19 | var seconds = parseInt(ev.target.dataset.seconds, 10); 20 | bgpage.set_reload(tab_id, seconds); 21 | window.close(); 22 | }); 23 | } 24 | 25 | function stop_this_tab_button_click_handler(ev) { 26 | run_with_tabId_and_bgPage(function(tab_id, bgpage) { 27 | bgpage.clear_reload(tab_id); 28 | window.close(); 29 | }); 30 | } 31 | 32 | function stop_all_tabs_button_click_handler(ev) { 33 | run_with_tabId_and_bgPage(function(tab_id, bgpage) { 34 | bgpage.clear_all_reloads(); 35 | window.close(); 36 | }); 37 | } 38 | 39 | function custom_form_submit_handler(ev) { 40 | ev.preventDefault(); 41 | run_with_tabId_and_bgPage(function(tab_id, bgpage) { 42 | var days = ev.target.days.valueAsNumber; 43 | var hours = ev.target.hours.valueAsNumber; 44 | var minutes = ev.target.minutes.valueAsNumber; 45 | var seconds = ev.target.seconds.valueAsNumber; 46 | 47 | var total = ((days * 24 + hours) * 60 + minutes) * 60 + seconds; 48 | 49 | bgpage.set_reload(tab_id, total); 50 | window.close(); 51 | }); 52 | ev.stopPropagation(); 53 | return false; 54 | } 55 | 56 | ////////////////////////////////////////////////////////////////////// 57 | // Fancy spinbox handling. 58 | // Also posted at: http://codepen.io/denilsonsa/pen/ZGYEEp?editors=101 59 | 60 | function custom_form_input_handler(ev) { 61 | var form = ev.currentTarget; 62 | 63 | if (form.seconds.valueAsNumber == -1) { 64 | if (form.minutes.valueAsNumber > 0 || form.hours.valueAsNumber > 0 || form.days.valueAsNumber > 0) { 65 | form.minutes.valueAsNumber--; 66 | form.seconds.valueAsNumber = 59; 67 | } else { 68 | form.seconds.valueAsNumber = 0; 69 | } 70 | } else if (form.seconds.valueAsNumber == 60) { 71 | form.minutes.valueAsNumber++; 72 | form.seconds.valueAsNumber = 0; 73 | } 74 | 75 | if (form.minutes.valueAsNumber == -1) { 76 | if (form.hours.valueAsNumber > 0 || form.days.valueAsNumber > 0) { 77 | form.hours.valueAsNumber--; 78 | form.minutes.valueAsNumber = 59; 79 | } else { 80 | form.minutes.valueAsNumber = 0; 81 | } 82 | } else if (form.minutes.valueAsNumber == 60) { 83 | form.hours.valueAsNumber++; 84 | form.minutes.valueAsNumber = 0; 85 | } 86 | 87 | if (form.hours.valueAsNumber == -1) { 88 | if (form.days.valueAsNumber > 0) { 89 | form.days.valueAsNumber--; 90 | form.hours.valueAsNumber = 23; 91 | } else { 92 | form.hours.valueAsNumber = 0; 93 | } 94 | } else if (form.hours.valueAsNumber == 24) { 95 | form.days.valueAsNumber++; 96 | form.hours.valueAsNumber = 0; 97 | } 98 | 99 | if (form.days.valueAsNumber == -1) { 100 | form.days.valueAsNumber = 0; 101 | } 102 | } 103 | 104 | ////////////////////////////////////////////////////////////////////// 105 | // Initialization. 106 | 107 | function init() { 108 | run_with_tabId_and_bgPage(function(tab_id, bgpage) { 109 | var total_reloads = bgpage.get_how_many_reloads_are_active(); 110 | var interval_seconds = bgpage.get_reload(tab_id); 111 | var interval = bgpage.split_seconds(interval_seconds); 112 | 113 | var total_reloads_string = total_reloads + ' tabs'; 114 | if (total_reloads == 0) { 115 | total_reloads_string = 'No tabs'; 116 | } else if (total_reloads == 1) { 117 | total_reloads_string = '1 tab'; 118 | } 119 | document.getElementById('number_of_reloading_tabs').value = total_reloads_string; 120 | if (total_reloads > 1 121 | || (total_reloads == 1 && interval_seconds == 0)) { 122 | document.getElementById('section_other').style.display = 'block'; 123 | } 124 | 125 | if (interval_seconds > 0) { 126 | document.getElementById('section_this_tab').style.display = 'block'; 127 | } 128 | 129 | var preset_buttons = document.querySelectorAll('input.preset_button'); 130 | for (var i = 0; i < preset_buttons.length; i++) { 131 | var button = preset_buttons[i]; 132 | if (button.dataset.seconds == interval_seconds) { 133 | button.classList.add('active'); 134 | button.disabled = true; 135 | } 136 | button.addEventListener('click', preset_button_click_handler); 137 | } 138 | 139 | var custom = document.getElementById('custom_form'); 140 | custom.addEventListener('submit', custom_form_submit_handler); 141 | custom.addEventListener('input', custom_form_input_handler); 142 | custom.days.value = interval.days; 143 | custom.hours.value = interval.hours; 144 | custom.minutes.value = interval.minutes; 145 | custom.seconds.value = interval.seconds; 146 | 147 | document.getElementById('stop_this_tab_button').addEventListener('click', stop_this_tab_button_click_handler); 148 | document.getElementById('stop_all_tabs_button').addEventListener('click', stop_all_tabs_button_click_handler); 149 | }); 150 | } 151 | 152 | // This script is being included with the "defer" attribute, which means it 153 | // will only be executed after the document has been parsed. 154 | init(); 155 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 37 | 46 | 50 | 54 | 55 | 59 | 69 | 76 | 79 | 86 | 93 | 94 | 97 | 104 | 111 | 112 | 115 | 120 | 125 | 130 | 135 | 141 | 147 | 153 | 159 | 165 | 171 | 177 | 183 | 189 | 190 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Note: Chrome provides an "alarms" API, but: 4 | // * It requires an extra permission: "alarms" 5 | // * The minimum interval is one minute. 6 | // https://developer.chrome.com/extensions/alarms 7 | // 8 | // Since this extension has sub-minute reload intervals, the "alarms" API can't 9 | // be used. That's why this extension uses a persistent background page with 10 | // setInterval/setTimeout. 11 | 12 | ////////////////////////////////////////////////////////////////////// 13 | // Internal data structure. 14 | 15 | function Reload(tab_id, seconds) { 16 | this.tab_id = tab_id; 17 | this.seconds = seconds; // Interval in seconds 18 | this.next_reload_timestamp = Date.now() + 1000 * seconds; // Milliseconds 19 | this.badge_interval_text = seconds_to_badge_text(seconds); 20 | this.interval_id = null; 21 | } 22 | 23 | Reload.prototype.toString = function() { 24 | return 'Reload(' + this.tab_id + ', ' + this.seconds + ' /* text=' + this.badge_interval_text + ', id=' + this.interval_id + ' */ )'; 25 | }; 26 | 27 | Reload.prototype.reload_tab = function() { 28 | this.next_reload_timestamp = Date.now() + 1000 * this.seconds; 29 | chrome.tabs.reload(this.tab_id); 30 | }; 31 | 32 | Reload.prototype.set_chrome_badge = function() { 33 | var badge_text; 34 | var badge_color = '#204a87'; 35 | if (!this.tab_id) { 36 | // Tab has been closed/removed. 37 | return; 38 | } 39 | if (!this.interval_id) { 40 | // Tab is no longer auto-reloading. 41 | badge_text = ''; 42 | } else if (g_should_display_badge_countdown && this.next_reload_timestamp > 0) { 43 | badge_color = '#4e9a06'; 44 | var delta = Math.round((this.next_reload_timestamp - Date.now()) / 1000); 45 | if (delta > 0) { 46 | badge_text = seconds_to_badge_text(delta); 47 | } else { 48 | badge_text = 'now'; 49 | } 50 | } else { 51 | badge_text = this.badge_interval_text; 52 | } 53 | chrome.browserAction.setBadgeText({ 54 | text: badge_text, 55 | tabId: this.tab_id 56 | }); 57 | chrome.browserAction.setBadgeBackgroundColor({ 58 | color: badge_color, 59 | tabId: this.tab_id 60 | }); 61 | }; 62 | 63 | Reload.prototype.clear = function() { 64 | clearInterval(this.interval_id); 65 | this.interval_id = null; 66 | this.next_reload_timestamp = 0; 67 | this.badge_interval_text = ''; 68 | this.set_chrome_badge(); 69 | }; 70 | 71 | 72 | ////////////////////////////////////////////////////////////////////// 73 | // Global variables. 74 | 75 | // Dictionary of currently active Reload objects. 76 | // g_active_reloads[tab_id] -> Reload() object 77 | var g_active_reloads = {}; 78 | var g_active_reloads_length = 0; 79 | 80 | // To avoid setting event listeners twice. 81 | var g_are_event_listeners_set = false; 82 | 83 | // Used to update the badge every second. 84 | var g_should_display_badge_countdown = true; // This value is overwritten in init(). 85 | var g_badge_countdown_interval_id = null; 86 | 87 | function update_chrome_badge_every_second() { 88 | var ids = Object.keys(g_active_reloads) 89 | for (var tab_id of ids) { 90 | var x = g_active_reloads[tab_id]; 91 | if (x) { 92 | x.set_chrome_badge(); 93 | } 94 | } 95 | } 96 | 97 | 98 | ////////////////////////////////////////////////////////////////////// 99 | // "External" API, called from popup.html. 100 | // 101 | // Essentially, manipulates g_active_reloads and calls the required functions. 102 | 103 | // Deletes a reload for this tab_id. 104 | // Does nothing if there is no reload for that tab. 105 | function clear_reload(tab_id, has_the_tab_been_removed) { 106 | var x = g_active_reloads[tab_id]; 107 | if (x) { 108 | if (has_the_tab_been_removed) { 109 | x.tab_id = null; 110 | } 111 | x.clear(); 112 | g_active_reloads_length--; 113 | set_or_clear_chrome_listeners(); 114 | } 115 | delete g_active_reloads[tab_id]; 116 | } 117 | 118 | // Clears all currently active reloads. 119 | function clear_all_reloads() { 120 | var ids = Object.keys(g_active_reloads) 121 | for (var tab_id of ids) { 122 | clear_reload(tab_id); 123 | } 124 | console.assert(g_active_reloads_length == 0); 125 | } 126 | 127 | // Sets a reload for a tab. 128 | // Clears/deletes the previous reload for that tab before setting a new one. 129 | function set_reload(tab_id, seconds) { 130 | clear_reload(tab_id); 131 | if (seconds > 0) { 132 | var x = new Reload(tab_id, seconds); 133 | g_active_reloads[tab_id] = x; 134 | g_active_reloads_length++; 135 | x.interval_id = setInterval(function() { 136 | x.reload_tab(); 137 | }, seconds * 1000); 138 | x.set_chrome_badge(); 139 | set_or_clear_chrome_listeners(); 140 | } 141 | } 142 | 143 | // Returns the amount of seconds of a reload of a tab. 144 | // Returns zero if no reload is set. 145 | function get_reload(tab_id) { 146 | var x = g_active_reloads[tab_id]; 147 | if (x) { 148 | return x.seconds; 149 | } 150 | return 0; 151 | } 152 | 153 | // Returns the number of active auto-reloads. 154 | function get_how_many_reloads_are_active() { 155 | return g_active_reloads_length; 156 | } 157 | 158 | // Stop the countdown display on the badge text. 159 | function stop_badge_countdown() { 160 | if (g_badge_countdown_interval_id) { 161 | clearInterval(g_badge_countdown_interval_id); 162 | g_badge_countdown_interval_id = null; 163 | } 164 | } 165 | 166 | // Start the countdown display on the badge text (if it is enabled). 167 | function start_badge_countdown() { 168 | stop_badge_countdown(); 169 | if (g_should_display_badge_countdown) { 170 | g_badge_countdown_interval_id = setInterval(update_chrome_badge_every_second, 1000); 171 | } 172 | } 173 | 174 | ////////////////////////////////////////////////////////////////////// 175 | // Misc. 176 | 177 | function split_seconds(seconds) { 178 | var minutes = Math.floor(seconds / 60); 179 | var hours = Math.floor(minutes / 60); 180 | var days = Math.floor(hours / 24); 181 | 182 | return { 183 | 'seconds': Math.floor(seconds) % 60, 184 | 'minutes': minutes % 60, 185 | 'hours': hours % 24, 186 | 'days': days 187 | }; 188 | } 189 | 190 | function seconds_to_badge_text(seconds) { 191 | var x = split_seconds(seconds); 192 | 193 | if (x.days > 9) { 194 | return x.days + 'd'; 195 | } else if (x.days > 0) { 196 | if (x.hours > 0) { 197 | // If 9 days and 23 hours, it gets rounded to 10.0. 198 | // I want it to display as '10d' instead of '10.0d'. 199 | let days = (x.days + (x.hours / 24.0)).toFixed(1); 200 | if (days.length > 3) { 201 | days = days.substring(0, days.indexOf('.')); 202 | } 203 | return days + 'd'; 204 | } else { 205 | return x.days + 'd'; 206 | } 207 | } else if (x.hours > 9) { 208 | return x.hours + 'h'; 209 | } else if (x.hours > 0) { 210 | if (x.minutes > 9) { 211 | return x.hours + 'h' + x.minutes; 212 | } else if (x.minutes > 0) { 213 | return x.hours + 'h' + '0' + x.minutes; 214 | } else { 215 | return x.hours + 'h'; 216 | } 217 | } else if (x.minutes > 0) { 218 | if (x.seconds > 9) { 219 | return x.minutes + '\'' + x.seconds; 220 | } else if (x.seconds > 0) { 221 | return x.minutes + '\'' + '0' + x.seconds; 222 | } else { 223 | return x.minutes + '\''; 224 | } 225 | } else if (x.seconds > 0) { 226 | return x.seconds + '"'; 227 | } else { 228 | return ''; 229 | } 230 | } 231 | 232 | ////////////////////////////////////////////////////////////////////// 233 | // Chrome listeners. 234 | 235 | function tabs_onUpdated_handler(tab_id, change_info, tab) { 236 | var x = g_active_reloads[tab_id]; 237 | if (x) { 238 | if (change_info.status == 'loading') { 239 | // Reload the badge text for this tab. 240 | // It gets cleared whenever the tab gets reloaded or loads another 241 | // page; so the extension needs to keep re-setting the badge. 242 | x.set_chrome_badge(); 243 | } 244 | } 245 | } 246 | 247 | function tabs_onRemoved_handler(tab_id, remove_info) { 248 | clear_reload(tab_id, true); 249 | } 250 | 251 | // Should be called immediately after g_active_reloads_length is changed. 252 | // Clears the listeners if there is no active reload. 253 | // Sets the listeners if one reload has been added. 254 | function set_or_clear_chrome_listeners() { 255 | if (g_active_reloads_length == 0) { 256 | chrome.tabs.onUpdated.removeListener(tabs_onUpdated_handler); 257 | chrome.tabs.onRemoved.removeListener(tabs_onRemoved_handler); 258 | stop_badge_countdown(); 259 | g_are_event_listeners_set = false; 260 | } 261 | else if (g_active_reloads_length == 1 && !g_are_event_listeners_set) { 262 | chrome.tabs.onUpdated.addListener(tabs_onUpdated_handler); 263 | chrome.tabs.onRemoved.addListener(tabs_onRemoved_handler); 264 | start_badge_countdown(); 265 | g_are_event_listeners_set = true; 266 | } 267 | } 268 | 269 | ////////////////////////////////////////////////////////////////////// 270 | // Context menu item. 271 | 272 | function init_context_menu_items() { 273 | chrome.contextMenus.create({ 274 | 'id': 'toggle_countdown', 275 | 'title': 'Show timer countdown', 276 | 'type': 'checkbox', 277 | 'checked': g_should_display_badge_countdown, 278 | 'contexts': ['browser_action'], 279 | }); 280 | chrome.contextMenus.onClicked.addListener(function(info, tab) { 281 | if (info.menuItemId === 'toggle_countdown') { 282 | g_should_display_badge_countdown = ! g_should_display_badge_countdown; 283 | chrome.contextMenus.update('toggle_countdown', { 284 | 'checked': g_should_display_badge_countdown, 285 | }); 286 | if (g_active_reloads_length > 0) { 287 | if (g_should_display_badge_countdown) { 288 | start_badge_countdown(); 289 | } else { 290 | stop_badge_countdown(); 291 | } 292 | update_chrome_badge_every_second(); 293 | } 294 | chrome.storage.local.set({'should_display_badge_countdown': g_should_display_badge_countdown}); 295 | } 296 | }); 297 | } 298 | 299 | function init() { 300 | chrome.storage.local.get(['should_display_badge_countdown'], function(result) { 301 | var value = result.should_display_badge_countdown; 302 | if (value === undefined) { 303 | value = true; 304 | } 305 | g_should_display_badge_countdown = value; 306 | init_context_menu_items(); 307 | }); 308 | } 309 | 310 | init(); 311 | -------------------------------------------------------------------------------- /promotional_440x280.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 25 | 27 | image/svg+xml 28 | 30 | 31 | 32 | 33 | 34 | 36 | 61 | 70 | 71 | 76 | 83 | 86 | 96 | 103 | 107 | 114 | 121 | 122 | 126 | 133 | 140 | 141 | 144 | 149 | 154 | 159 | 164 | 170 | 176 | 182 | 188 | 194 | 200 | 206 | 212 | 218 | 219 | 224 | 225 | Tab AutoReloader 241 | 244 | 260 | Open-source!No permissions! 275 | 276 | 277 | 278 | --------------------------------------------------------------------------------