├── LICENSE ├── README ├── icons ├── load_progress_bar-48.png └── load_progress_bar-96.png ├── js └── load_progress_bar.js ├── make.sh ├── manifest.json └── settings ├── options.html └── options.js /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Luben Karavelov. 2 | All rights reserved. 3 | 4 | BSD License 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, this 13 | list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Addon for Firefox to add minimalistic load progress bar to each page. 2 | -------------------------------------------------------------------------------- /icons/load_progress_bar-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luben/load-progress-bar/e811d79927374b3fc13c535fe8aad7fd5d582801/icons/load_progress_bar-48.png -------------------------------------------------------------------------------- /icons/load_progress_bar-96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/luben/load-progress-bar/e811d79927374b3fc13c535fe8aad7fd5d582801/icons/load_progress_bar-96.png -------------------------------------------------------------------------------- /js/load_progress_bar.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | var inserted = 0; 5 | var loaded = 0; 6 | var last_pct = 1; 7 | var last_ts = 0; 8 | var finished = false; 9 | var setup_done = false; 10 | var css = null; 11 | 12 | // load settings 13 | var settings; 14 | browser.storage.local.get({ 15 | color: "#FF0000", 16 | isRainbow: false, 17 | rainbow_size: 3.0, 18 | width: "2", 19 | opacity: "0.75", 20 | place: "top", 21 | smooth: "no" 22 | }).then((item) => { 23 | if (css != null) { 24 | setupCss(item) 25 | } else { 26 | settings = item 27 | } 28 | }, onError); 29 | 30 | const listenerCfg = {"once": true, "capture": true, "passive": true}; 31 | 32 | function onError(error) { 33 | console.log(`Error: ${error}`); 34 | } 35 | 36 | let observer = new MutationObserver((mutations) => { 37 | mutations.forEach((mutation) => { 38 | mutation.addedNodes.forEach((node) => { 39 | if (node.nodeName == "BODY") { 40 | inserted++; 41 | node.addEventListener( "load", () => onLoadHandler(node), listenerCfg); 42 | updateProgress(); 43 | } else if (((node.nodeName == "SCRIPT" || 44 | node.nodeName == "VIDEO" || node.nodeName == "IMG" || 45 | node.nodeName == "IFRAME" || node.nodeName == "FRAME") && node.src != "" ) || 46 | (node.nodeName == "LINK" && node.rel == "stylesheet" && window.matchMedia(node.media)) 47 | ) { 48 | inserted++; 49 | node.addEventListener( "error", () => onLoadHandler(node), listenerCfg); 50 | node.addEventListener( "abort", () => onLoadHandler(node), listenerCfg); 51 | node.addEventListener( "load", () => onLoadHandler(node), listenerCfg); 52 | updateProgress(); 53 | } 54 | }); 55 | }); 56 | }); 57 | window.addEventListener( "load", () => onLoadHandler(window), listenerCfg); 58 | observer.observe(document, {childList: true, subtree: true}); 59 | 60 | function hexToRgbA(hex){ 61 | var c; 62 | if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){ 63 | c= hex.substring(1).split(''); 64 | if(c.length == 3){ 65 | c= [c[0], c[0], c[1], c[1], c[2], c[2]]; 66 | } 67 | c= "0x" + c.join(""); 68 | return "rgba("+[(c>>16)&255, (c>>8)&255, c&255].join(",")+",1)"; 69 | } 70 | return "rgba(255,0,0,1)"; 71 | } 72 | 73 | function setupCss(settings) { 74 | setup_done = true; 75 | let color = hexToRgbA(settings.color); 76 | let isRainbow = settings.isRainbow; 77 | let rainbow_size = settings.rainbow_size; 78 | let opacity = settings.opacity; 79 | let transition = ((settings.smooth == "yes") ? "right 0.5s linear, " : ""); 80 | 81 | let cssStyles = 82 | ` 83 | html:before { 84 | background: ${color}; 85 | opacity: ${opacity}; 86 | transition: ${transition} opacity 0.85s ease-out; 87 | position: fixed; 88 | content: ""; 89 | z-index: 2147483647; 90 | ${settings.place}: 0; 91 | left: 0; 92 | height: ${settings.width}px; 93 | `; 94 | if(isRainbow){ 95 | cssStyles += 96 | ` 97 | background: linear-gradient(124deg, #ff2400, #e81d1d, #e8b71d, #e3e81d, #1de840, #1ddde8, #2b1de8, #dd00f3, #dd00f3, #f30059, #ff2400); 98 | background-size: ${rainbow_size*100}% ${rainbow_size*100}%; 99 | 100 | -webkit-animation: rainbow 18s ease infinite; 101 | -z-animation: rainbow 18s ease infinite; 102 | -o-animation: rainbow 18s ease infinite; 103 | animation: rainbow 18s ease infinite;} 104 | 105 | @-webkit-keyframes rainbow { 106 | 0%{background-position:0% 82%} 107 | 50%{background-position:100% 19%} 108 | 100%{background-position:0% 82%} 109 | } 110 | @-moz-keyframes rainbow { 111 | 0%{background-position:0% 82%} 112 | 50%{background-position:100% 19%} 113 | 100%{background-position:0% 82%} 114 | } 115 | @-o-keyframes rainbow { 116 | 0%{background-position:0% 82%} 117 | 50%{background-position:100% 19%} 118 | 100%{background-position:0% 82%} 119 | } 120 | @keyframes rainbow { 121 | 0%{background-position:0% 82%} 122 | 50%{background-position:100% 19%} 123 | 100%{background-position:0% 82%} 124 | } 125 | `; 126 | } 127 | cssStyles += `}`; 128 | css.appendChild(document.createTextNode(cssStyles)); 129 | } 130 | 131 | function updateProgress() { 132 | if (document.body != null && !finished) { 133 | if (css == null) { 134 | css = document.createElement('style'); 135 | css.type = 'text/css'; 136 | css.appendChild(document.createTextNode(` 137 | html:before { 138 | right: 99%; 139 | } 140 | `)); 141 | document.body.appendChild(css); 142 | } 143 | if (settings != null && !setup_done) { 144 | setupCss(settings); 145 | } 146 | 147 | const pct = 100 - (inserted - loaded) * 100 / inserted; 148 | const ts = Date.now() 149 | if (pct <= 100 && pct > last_pct && ts >= last_ts + 250) { 150 | last_pct = pct; 151 | last_ts = ts; 152 | const space = 100 - pct; 153 | css.firstChild.replaceWith(document.createTextNode(` 154 | html:before { 155 | right: ${space}%; 156 | } 157 | `)); 158 | } 159 | } 160 | } 161 | 162 | function onLoadHandler(node) { 163 | loaded++; 164 | updateProgress() 165 | if (!finished && node.self == node && css != null) { // i.e. the window is loaded 166 | finished = true; 167 | observer.disconnect(); 168 | css.firstChild.replaceWith(document.createTextNode(` 169 | html:before { 170 | right: 0; 171 | } 172 | `)); 173 | setTimeout(function() { 174 | css.firstChild.replaceWith(document.createTextNode(` 175 | html:before { 176 | right: 0; 177 | opacity: 0 !important; 178 | } 179 | `)); 180 | setTimeout(function() { 181 | css.firstChild.replaceWith(document.createTextNode(` 182 | html:before { 183 | z-index: -2147483646; 184 | }`) 185 | )}, 850); 186 | }, 150); 187 | } 188 | } 189 | 190 | })(); 191 | -------------------------------------------------------------------------------- /make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | rm -f load-progress-bar.zip 3 | zip -r load-progress-bar.zip . -x '*.git*' -x '*.swp' -x 'make.sh' -x 'index.html' 4 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Load Progress Bar", 4 | "version": "0.25", 5 | "description": "Adds minimalistic load progress bar on each page", 6 | "homepage_url": "https://github.com/luben/load-progress-bar", 7 | 8 | "applications": { 9 | "gecko": { 10 | "id": "load-progress-bar@luben.github.com", 11 | "strict_min_version": "58.0" 12 | } 13 | }, 14 | 15 | "icons": { 16 | "48": "icons/load_progress_bar-48.png", 17 | "96": "icons/load_progress_bar-96.png" 18 | }, 19 | 20 | "content_scripts": [ 21 | { 22 | "matches": ["http://*/*", "https://*/*"], 23 | "js": ["js/load_progress_bar.js"], 24 | "run_at": "document_start" 25 | } 26 | ], 27 | 28 | "options_ui": { 29 | "page": "settings/options.html" 30 | }, 31 | 32 | "permissions": ["storage"] 33 | } 34 | -------------------------------------------------------------------------------- /settings/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 18 | 19 | 20 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /settings/options.js: -------------------------------------------------------------------------------- 1 | function saveOptions(e) { 2 | e.preventDefault(); 3 | browser.storage.local.set({ 4 | "color": document.querySelector("#color").value, 5 | "width": document.querySelector("#width").value, 6 | "isRainbow": document.querySelector("#isRainbow").checked, 7 | "rainbow_size": document.querySelector("#rainbow_size").value, 8 | "opacity": document.querySelector("#opacity").value, 9 | "place": document.querySelector("#place").value, 10 | "smooth": document.querySelector("#smooth").value 11 | }); 12 | } 13 | 14 | function restoreOptions() { 15 | 16 | function updateSettings(result) { 17 | document.querySelector("#color").value = result.color; 18 | document.querySelector("#width").value = result.width; 19 | document.querySelector("#isRainbow").checked = result.isRainbow, 20 | document.querySelector("#rainbow_size").value = result.rainbow_size, 21 | document.querySelector("#opacity").value = result.opacity; 22 | document.querySelector("#place").value = result.place; 23 | document.querySelector("#smooth").value = result.smooth; 24 | } 25 | 26 | function onError(error) { 27 | console.log(`Error: ${error}`); 28 | } 29 | 30 | browser.storage.local.get({ 31 | color: "#FF0000", 32 | isRainbow: true, 33 | rainbow_size: 3, 34 | width: "2", 35 | opacity: "0.75", 36 | place: "top", 37 | smooth: "no" 38 | }).then(updateSettings, onError); 39 | } 40 | 41 | document.addEventListener("DOMContentLoaded", restoreOptions); 42 | document.querySelector("form").addEventListener("submit", saveOptions); 43 | document.querySelectorAll("select").forEach((i) => i.addEventListener("change", saveOptions)); 44 | document.querySelectorAll("input").forEach((i) => i.addEventListener("change", saveOptions)); 45 | --------------------------------------------------------------------------------