├── .gitignore ├── icon.png ├── weeicon.png ├── .gitattributes ├── manifest.json ├── MIT-LICENSE ├── README.md ├── options.js ├── options.html ├── background.js ├── db.js └── check.js /.gitignore: -------------------------------------------------------------------------------- 1 | ## generic files to ignore 2 | *~ 3 | *.DS_Store 4 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/Check-My-Links/master/icon.png -------------------------------------------------------------------------------- /weeicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kyleladd/Check-My-Links/master/weeicon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "options_page": "options.html", 3 | "browser_action": { 4 | "default_icon": "weeicon.png", 5 | "default_title": "Check My Links Redux" 6 | }, 7 | "description": "Check My Links Redux is a link checker that crawls through your webpage and looks for broken links.", 8 | "icons": { "128": "icon.png" }, 9 | "name": "Check My Links Redux", 10 | "permissions": [ "storage","tabs", "http://*/*", "https://*/*" ], 11 | "version": "4.0.0", 12 | "manifest_version": 2, 13 | "background": { "scripts": ["background.js","db.js"],"persistent": true } 14 | } 15 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Paul Livingstone 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Check My Links 2 | 3 | A Chrome Extension which checks links on webpages and shows their HTTP response codes. This allows web content editors to quickly see which links are broken (for whatever reason) and which resolve correctly. Check My Links is an extension developed primarily for web designers, developers, and content editors that crawls through your webpages looking for broken links and identifying them for you. 4 | 5 | ### Installation 6 | This extension can be found in the Chrome Web Store [https://chrome.google.com/webstore/detail/check-my-links-redux/pmklcclkpjcfmfnmjocmocmgkmkamddk](https://chrome.google.com/webstore/detail/check-my-links-redux/pmklcclkpjcfmfnmjocmocmgkmkamddk) 7 | 8 | ### Instructions 9 | 10 | To use the extension: 11 | 12 | 1. Browse to the page you want to check 13 | 2. Click on the 'Check My Links' button in the Chrome toolbar 14 | 3. The extension will then find all of the links on a web page, and check each one for you. Broken links will be shown on the page highlighted in red with the server response code next to the link (404, 500 etc). 15 | 16 | ### Options 17 | #### Blacklisting 18 | To prevent this extension from checking a link, please enter all or part of the URL in the blacklist below 19 | #### Request Type 20 | HEAD is quicker, but sometimes doesn't reflect the true HTTP status of a page, switch to GET if you're getting weird results. 21 | #### Caching 22 | With caching enabled, valid responses will be stored within the browser's IndexedDB. In future requests to that same link, Check My Links Redux will get the status from the database opposed to making another request. 23 | #### NoFollow 24 | With this enabled, links with rel="nofollow" attribute will be checked. 25 | #### Auto Check 26 | With this enabled, the link checker will automatically start when the user browses to a new page or refreshes. 27 | 28 | ### Issues 29 | 30 | If you want to report an issue, bug or make a suggestion, please do so here: 31 | 32 | [https://github.com/kyleladd/Check-My-Links/issues](https://github.com/kyleladd/Check-My-Links/issues) 33 | 34 | ### The Original Check My Links 35 | 36 | The original Check My Links can be found here: 37 | 38 | [https://github.com/ocodia/Check-My-Links/](https://github.com/ocodia/Check-My-Links/) 39 | 40 | You can install a packaged version of the original extension here: 41 | 42 | [https://chrome.google.com/webstore/detail/check-my-links/ojkcdipcgfaekbeaelaapakgnjflfglf](https://chrome.google.com/webstore/detail/check-my-links/ojkcdipcgfaekbeaelaapakgnjflfglf) 43 | 44 | ### License 45 | 46 | Check My Links is released under the MIT license. 47 | 48 | [www.opensource.org/licenses/MIT](www.opensource.org/licenses/MIT) 49 | 50 | Thanks 51 | 52 | -------------------------------------------------------------------------------- /options.js: -------------------------------------------------------------------------------- 1 | // Check My Links by Paul Livingstone 2 | // @ocodia 3 | 4 | function loadOptions() { 5 | 6 | var bkg = chrome.extension.getBackgroundPage(); 7 | var blacklistItems = bkg.getItem("blacklist"); 8 | var checkTypeSelection = bkg.getItem("checkType"); 9 | var cache = bkg.getItem("cache"); 10 | var autoCheck = bkg.getItem("autoCheck"); 11 | var noFollow = bkg.getItem("noFollow"); 12 | 13 | if (blacklistItems === null) { 14 | bkg.setItem("blacklist", bkg.blacklistDefaults); 15 | blacklistItems = bkg.getItem("blacklist"); 16 | } 17 | 18 | if (checkTypeSelection === null) { 19 | bkg.setItem("checkType", bkg.checkTypeDefault); 20 | checkTypeSelection = bkg.getItem("checkType"); 21 | } 22 | if (cache === null) { 23 | bkg.setItem("cache", bkg.cacheDefault); 24 | cache = bkg.getItem("cache"); 25 | } 26 | 27 | if(blacklistItems !== null){ 28 | blacklistItems.split(" "); 29 | } 30 | 31 | if(cache == 'true'){ 32 | document.getElementById("cache").checked = true; 33 | } 34 | 35 | if(bkg.getItem("autoCheck") == null){ 36 | bkg.setItem("autoCheck", bkg.autoCheckDefault); 37 | autoCheck = bkg.getItem("autoCheck"); 38 | } 39 | if(autoCheck == 'true'){ 40 | document.getElementById("autoCheck").checked = true; 41 | } 42 | 43 | if (bkg.getItem("noFollow") == null) { 44 | bkg.setItem("noFollow", bkg.noFollowDefault); 45 | noFollow = bkg.getItem("noFollow"); 46 | } 47 | 48 | if(noFollow == 'true'){ 49 | document.getElementById("noFollow").checked = true; 50 | } 51 | 52 | document.getElementById("blacklistEntries").value = blacklistItems; 53 | var requestType = document.getElementById("requestType"); 54 | 55 | // Select the appropriate saved option for TRENDS 56 | for (var i = 0; i < requestType.children.length; i++) { 57 | var requestTypechild = requestType.children[i]; 58 | if (requestTypechild.value == checkTypeSelection) { 59 | requestTypechild.selected = "true"; 60 | break; 61 | } 62 | } 63 | } 64 | 65 | function saveOptions() { 66 | var bkg = chrome.extension.getBackgroundPage(); 67 | var blacklistEntries = document.getElementById("blacklistEntries"); 68 | var requestType = document.getElementById("requestType"); 69 | var cache = 'false'; 70 | var noFollow = 'false'; 71 | if(document.getElementById("cache").checked){ 72 | cache = 'true'; 73 | } 74 | var autoCheck = 'false'; 75 | if(document.getElementById("autoCheck").checked){ 76 | autoCheck = 'true'; 77 | } 78 | if(document.getElementById("noFollow").checked){ 79 | noFollow = 'true'; 80 | } 81 | // Save selected options to localstore 82 | bkg.setItem("blacklist", blacklistEntries.value); 83 | bkg.setItem("checkType", requestType.children[requestType.selectedIndex].value); 84 | bkg.setItem("cache", cache); 85 | bkg.setItem("autoCheck", autoCheck); 86 | bkg.setItem("noFollow", noFollow); 87 | document.getElementById("msg").style.visibility = "visible"; 88 | } 89 | function deleteObjectStore(){ 90 | indexedDBHelper.deleteObjectStore(); 91 | console.log("Cleared IndexedDB Datastore"); 92 | } 93 | 94 | document.getElementById('save').addEventListener('click', saveOptions); 95 | document.getElementById('clearCache').addEventListener('click', deleteObjectStore); 96 | 97 | loadOptions(); -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Check My Links Redux: Options 4 | 5 | 140 | 141 | 142 |
143 |

Check My Links Redux: Options

144 |

To prevent this extension from checking a link, please enter all or part of the URL in the blacklist below. Each URL must be entered on a new line.

145 |

For example: www.mydomain.com will prevent the following links from being checked: www.mydomain.com/page1.html or www.mydomain.com/page2.html

146 |
147 | 148 |
149 |
150 |
151 |
152 |
153 | 157 |
158 |
159 |

HEAD is quicker, but sometimes doesn't reflect the true HTTP status of a page, switch to GET if you're getting weird results.

160 | 161 |
162 | Cache results:
163 | 164 |
165 | Auto Check:
166 |
167 | Follow Links with Rel NoFollow:
168 |
169 |
170 |
171 | 172 | 173 |
174 | 175 | 178 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | var logging = false; 2 | var blacklistDefaults = 3 | "googleleads.g.doubleclick.net\n" + 4 | "doubleclick.net\n" + 5 | "googleadservices.com\n" + 6 | "www.googleadservices.com\n" + 7 | "googlesyndication.com\n" + 8 | "adservices.google.com\n" + 9 | "appliedsemantics.com"; 10 | 11 | var checkTypeDefault = "GET"; 12 | var cacheDefault = "false"; 13 | var autoCheckDefault = "false"; 14 | var noFollowDefault = "false"; 15 | chrome.extension.onMessage.addListener(onRequest); 16 | 17 | var beginLinkCheck = function beginLinkCheck(tab) { 18 | chrome.tabs.executeScript(tab.id, {file:'check.js'}, function () { 19 | // Set up the defaults if no values are present in LocalStorage 20 | if (getItem("blacklist") === null) { 21 | setItem("blacklist", blacklistDefaults); 22 | } 23 | 24 | if(getItem("checkType") == null){ 25 | setItem("checkType", checkTypeDefault); 26 | } 27 | 28 | if(getItem("cache") == null){ 29 | setItem("cache", cacheDefault); 30 | } 31 | var blacklist = getItem("blacklist"); 32 | var nofollow = getItem("noFollow"); 33 | 34 | chrome.tabs.sendMessage(tab.id, {bl:blacklist,nf:nofollow}); 35 | }); 36 | } 37 | chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) { 38 | var url = tab.url; 39 | 40 | if (url !== undefined && changeinfo.status == "complete" && getItem("autoCheck")=="true") { 41 | beginLinkCheck(tab); 42 | } 43 | }); 44 | 45 | chrome.browserAction.onClicked.addListener(beginLinkCheck); 46 | 47 | function onRequest(request, sender, callback) { 48 | if (request.action == "check") { 49 | if (request.url) { 50 | if (getItem("cache")=='true'){ 51 | indexedDBHelper.getLink(request.url).then(function(link){ 52 | if(typeof(link) != "undefined" && (200 <= link.status && link.status < 400)){ 53 | log("found"); 54 | log(link); 55 | return callback(link.status); 56 | } 57 | else{ 58 | check(request.url, callback); 59 | log("not in db"); 60 | log(request.url); 61 | log("added"); 62 | } 63 | }, function(err){ 64 | log(err); 65 | }); 66 | } 67 | else{ 68 | // do not use cache 69 | check(request.url, callback); 70 | } 71 | } 72 | } 73 | return true; 74 | } 75 | 76 | // Timeout for each link is 60+1 seconds 77 | var timeout = 30000; 78 | 79 | function check(url, callback) { 80 | var XMLHttpTimeout = null; 81 | var xhr = new XMLHttpRequest(); 82 | xhr.onreadystatechange = function (data) { 83 | if (xhr.readyState == 4) { 84 | log(xhr); 85 | clearTimeout(XMLHttpTimeout); 86 | if (200 <= xhr.status && xhr.status < 400){ 87 | if(url.indexOf("#")!=-1){ 88 | var parser = new DOMParser (); 89 | var responseDoc = parser.parseFromString (xhr.responseText, "text/html"); 90 | log (responseDoc.getElementById(url.substring(url.indexOf("#")+1,url.length))); 91 | if(responseDoc.getElementById(url.substring(url.indexOf("#")+1,url.length))){ 92 | // Element with id that matches hashtag was found 93 | if(getItem("cache")=='true'){ 94 | indexedDBHelper.addLink(url, xhr.status); 95 | } 96 | return callback(xhr.status); 97 | } 98 | else{ 99 | // Page resolved, but element with id that matches hashtag was not found 100 | return callback(404); 101 | } 102 | } 103 | else{ 104 | // No hashtag 105 | if (getItem("cache")=='true'){ 106 | indexedDBHelper.addLink(url, xhr.status); 107 | } 108 | } 109 | } 110 | return callback(xhr.status); 111 | } 112 | }; 113 | 114 | try { 115 | xhr.open(getItem("checkType"), url, true); 116 | xhr.send(); 117 | } 118 | catch(e){ 119 | console.log(e); 120 | } 121 | 122 | XMLHttpTimeout=setTimeout(function (){return callback(408); xhr.abort();}, timeout += 1000); 123 | } 124 | 125 | // OPTIONS: Management 126 | 127 | // OPTIONS: Set items in localstore 128 | function setItem(key, value) { 129 | try { 130 | log("Inside setItem:" + key + ":" + value); 131 | window.localStorage.removeItem(key); 132 | window.localStorage.setItem(key, value); 133 | }catch(e) { 134 | log("Error inside setItem"); 135 | log(e); 136 | } 137 | log("Return from setItem" + key + ":" + value); 138 | } 139 | 140 | // OPTIONS: Get items from localstore 141 | function getItem(key) { 142 | var value; 143 | log('Get Item:' + key); 144 | try { 145 | value = window.localStorage.getItem(key); 146 | }catch(e) { 147 | log("Error inside getItem() for key:" + key); 148 | log(e); 149 | value = "null"; 150 | } 151 | log("Returning value: " + value); 152 | return value; 153 | } 154 | 155 | // OPTIONS: Zap all items in localstore 156 | function clearStrg() { 157 | log('about to clear local storage'); 158 | window.localStorage.clear(); 159 | log('cleared'); 160 | } 161 | 162 | function log(txt) { 163 | if(logging) { 164 | console.log(txt); 165 | } 166 | } 167 | 168 | -------------------------------------------------------------------------------- /db.js: -------------------------------------------------------------------------------- 1 | var indexedDBHelper = function(){ 2 | var db = null; 3 | var lastIndex = 0; 4 | 5 | function init() { 6 | //open the database 7 | indexedDBHelper.open(); 8 | } 9 | 10 | var open = function(){ 11 | var version = 1; 12 | 13 | var promise = new Promise(function(resolve, reject){ 14 | //Opening the DB 15 | var request = indexedDB.open("CheckLinks", version); 16 | 17 | //Will be called if the database is new or the version is modified 18 | request.onupgradeneeded = function(e) { 19 | db = e.target.result; 20 | 21 | e.target.transaction.onerror = indexedDB.onerror; 22 | 23 | //Deleting DB if already exists 24 | if(db.objectStoreNames.contains("links")) { 25 | db.deleteObjectStore("links"); 26 | } 27 | 28 | //Creating a new DB store with a paecified key property 29 | var store = db.createObjectStore("links", 30 | {keyPath: "id"}); 31 | var linkIndex = store.createIndex("by_link", "link"); 32 | }; 33 | 34 | //If opening DB succeeds 35 | request.onsuccess = function(e) { 36 | db = e.target.result; 37 | resolve(); 38 | }; 39 | 40 | //If DB couldn't be opened for some reason 41 | request.onerror = function(e){ 42 | reject("Couldn't open DB"); 43 | }; 44 | }); 45 | return promise; 46 | }; 47 | 48 | var addLink = function(linkURL,linkstatus) { 49 | //Creating a transaction object to perform read-write operations 50 | var trans = db.transaction(["links"], "readwrite"); 51 | var store = trans.objectStore("links"); 52 | lastIndex++; 53 | 54 | //Wrapping logic inside a promise 55 | var promise = new Promise(function(resolve, reject){ 56 | //Sending a request to add an item 57 | var request = store.put({ 58 | "id": lastIndex, 59 | "link": linkURL, 60 | "timeStamp": new Date().getTime(), 61 | "status": linkstatus 62 | }); 63 | 64 | //success callback 65 | request.onsuccess = function(e) { 66 | resolve(); 67 | }; 68 | 69 | //error callback 70 | request.onerror = function(e) { 71 | console.log(e.value); 72 | reject("Couldn't add the passed item"); 73 | }; 74 | }); 75 | 76 | return promise; 77 | }; 78 | 79 | var getAllLinks = function() { 80 | var linksArr = []; 81 | //Creating a transaction object to perform Read operations 82 | var trans = db.transaction(["links"], "readonly"); 83 | //Getting a reference of the link store 84 | var store = trans.objectStore("links"); 85 | 86 | //Wrapping all the logic inside a promise 87 | var promise = new Promise(function(resolve, reject){ 88 | //Opening a cursor to fetch items from lower bound in the DB 89 | var keyRange = IDBKeyRange.lowerBound(0); 90 | var cursorRequest = store.openCursor(keyRange); 91 | 92 | //success callback 93 | cursorRequest.onsuccess = function(e) { 94 | var result = e.target.result; 95 | 96 | //Resolving the promise with link items when the result id empty 97 | if(result === null || result === undefined){ 98 | resolve(linksArr); 99 | } 100 | //Pushing result into the link list 101 | else{ 102 | linksArr.push(result.value); 103 | if(result.value.id > lastIndex){ 104 | lastIndex = result.value.id; 105 | } 106 | result.continue(); 107 | } 108 | }; 109 | 110 | //Error callback 111 | cursorRequest.onerror = function(e){ 112 | reject("Couldn't fetch items from the DB"); 113 | }; 114 | }); 115 | return promise; 116 | }; 117 | 118 | var deleteObjectStore = function(id) { 119 | indexedDBHelper.open().then(function(){ 120 | var promise = new Promise(function(resolve, reject){ 121 | var trans = db.transaction(["links"], "readwrite"); 122 | var store = trans.objectStore("links"); 123 | var request = store.clear(); 124 | 125 | request.onsuccess = function(e) { 126 | resolve(); 127 | }; 128 | 129 | request.onerror = function(e) { 130 | console.log(e); 131 | reject("Couldn't delete the item"); 132 | }; 133 | }); 134 | return promise; 135 | }, function(err){ 136 | console.log(err); 137 | }); 138 | }; 139 | 140 | var getLink = function(url) { 141 | var linksArr = []; 142 | //Creating a transaction object to perform Read operations 143 | var trans = db.transaction(["links"], "readonly"); 144 | //Getting a reference of the link store 145 | var store = trans.objectStore("links"); 146 | 147 | //Wrapping all the logic inside a promise 148 | var promise = new Promise(function(resolve, reject){ 149 | var index = store.index("by_link"); 150 | var request = index.get(url); 151 | 152 | //success callback 153 | request.onsuccess = function(e) { 154 | var result = e.target.result; 155 | resolve(result); 156 | }; 157 | 158 | //Error callback 159 | request.onerror = function(e){ 160 | reject("Couldn't fetch items from the DB"); 161 | }; 162 | }); 163 | return promise; 164 | }; 165 | 166 | var deleteLink = function(id) { 167 | 168 | var promise = new Promise(function(resolve, reject){ 169 | var trans = db.transaction(["links"], "readwrite"); 170 | var store = trans.objectStore("links"); 171 | var request = store.delete(id); 172 | 173 | request.onsuccess = function(e) { 174 | resolve(); 175 | }; 176 | 177 | request.onerror = function(e) { 178 | console.log(e); 179 | reject("Couldn't delete the item"); 180 | }; 181 | }); 182 | 183 | return promise; 184 | }; 185 | 186 | return{ 187 | init: init, 188 | open: open, 189 | addLink: addLink, 190 | getLink: getLink, 191 | getAllLinks: getAllLinks, 192 | deleteLink: deleteLink, 193 | deleteObjectStore: deleteObjectStore 194 | }; 195 | 196 | }(); 197 | 198 | indexedDBHelper.init(); -------------------------------------------------------------------------------- /check.js: -------------------------------------------------------------------------------- 1 | // Check My Links by Paul Livingstone 2 | // @ocodia 3 | 4 | String.prototype.startsWith = function(text) { 5 | return this.substr(0, text.length) == text; 6 | }; 7 | 8 | String.prototype.contains = function(text) { 9 | return this.indexOf(text) !== -1; 10 | }; 11 | 12 | function removeClassFromElements(classname) { 13 | var x = document.getElementsByClassName(classname); 14 | var i; 15 | for (i = 0; i < x.length; i++) { 16 | x[i].classList.remove(classname); 17 | } 18 | } 19 | 20 | function removeDOMElement(id){ 21 | if(document.getElementById(id)){ 22 | document.getElementById(id).remove(); 23 | } 24 | } 25 | 26 | function removeElementsByClass(className){ 27 | var elements = document.getElementsByClassName(className); 28 | while(elements.length > 0){ 29 | elements[0].parentNode.removeChild(elements[0]); 30 | } 31 | } 32 | 33 | chrome.extension.onMessage.addListener( 34 | 35 | function doStuff(request, sender) { 36 | // Gather links 37 | var pageLinks = document.getElementsByTagName('a'); 38 | var totalvalid = pageLinks.length; 39 | var queued = 0; 40 | var checked = 0; 41 | var invalid = 0; 42 | var passed = 0; 43 | var rpBox; 44 | // Clear the Previous Run 45 | removeDOMElement("CMY_ReportBox"); 46 | removeElementsByClass("CMY_Response"); 47 | removeClassFromElements("CMY_Link"); 48 | removeClassFromElements("CMY_Valid"); 49 | removeClassFromElements("CMY_Invalid"); 50 | 51 | ( 52 | function(pg){ 53 | 54 | var blacklist = request.bl; 55 | blacklist = blacklist.split("\n"); 56 | 57 | var blacklisted; 58 | 59 | // Inject Styles and Elements for feedback report 60 | 61 | var reportStyle = document.createElement("style"); 62 | reportStyle.setAttribute("rel", "stylesheet"); 63 | reportStyle.setAttribute("type", "text/css"); 64 | document.getElementsByTagName("head")[0].appendChild(reportStyle); 65 | 66 | reportStyle.appendChild(document.createTextNode(".CMY_Link{background: #333 !important; color: #fff !important; font-weight: bold; border-right: 1px solid #fff;}")); 67 | reportStyle.appendChild(document.createTextNode(".CMY_Invalid{background: #990000 !important; color: #fff !important;}")); 68 | reportStyle.appendChild(document.createTextNode(".CMY_Valid{background: #669900 !important; color: #fff !important;}")); 69 | reportStyle.appendChild(document.createTextNode(".CMY_Response{color:#fff; border-left: 1px solid #fff; padding: 0px 5px; margin: 0px 0px 0px 5px;}")); 70 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox{font-weight: bold; width: 180px; position: fixed; right:0px; top: 0px; background: #fff; margin: 20px; padding: 0px; font-family: Arial, Helvetica, sans-serif; font-size: 14px; line-height: 14px; border-radius: 5px; z-index: 99999; box-shadow: 0px 0px 3px(0,0,0,0.2);}")); 71 | reportStyle.appendChild(document.createTextNode("#CMY_RB_Header{border-radius: 5px 5px 0px 0px; border-bottom: 1px solid #444; background: -webkit-gradient(linear, center top, center bottom, from(#777), to(#3B3B3B)); text-align: center; color: #000; font-size: 12px; padding: 5px; margin: 0px; text-shadow: 1px 1px 1px #666;}")); 72 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox .CMY_RB_ResultCount{font-size: 14px; box-sizing: border-box; width: 50%; color: #fff; font-weight: bold; line-height: 14px; text-shadow: 0px 0px 4px rgba(0,0,0,0.3); text-align: left; padding: 10px 10px 10px 40px; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABUAAABQCAYAAAD2gj5JAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAR2SURBVHja7JjdaxxVGMbPObM7HzszuwkoVdv+DSJCK2oilaz1o4KBJqYVTW+0ou2NjZZaQ2vTIoGoTa010gixjbtJm1KF9qZ3ghdeFgreeKeIopjd7Mxukt3sOT5nsklnZ2aTGPVCmIHDZnfe85vnvO/znjCHCiHIv33RGLrpyV29dp+SJPlEgsqv3Te/LH39j6ASmNDIZdNmCd1k8qfqvMv3VRx+c1NQAHsBnAKQ2m0KUQ1PKVmc5/NukWcTm1T4lR/IPKGkrmr0mGawOyFo14v2IFPIlls551AL4CSALADknJOBWpWcy31YEE3LB/AdPG1YNymhlHx67cLcocCS8xFAITh5a3FBnJs4M8ubCgXguwCeMTOMmGkmoQRJPz89Wjyc7bN7lARymA4rBPAIgJ8AWA9ZavdL9s9WG9uWbsckbTnxtaog5RL/bqEidiRVolqZMPDzwT/PBtPEVumM7Esk6S9Q5KnEd5JUKbEy7PG2e5hqt4eWLBWOrmv+F17L7LLbWd4w2RYUa/kSkiAjlx/WUDjQyGF9Qx3VfTDTabWznJFiWz1VtOl2HVU+Ul0Gir/VpgB3IL85PcW2KcoqeKXKo2sB12xTgHdJMFJxHxTXETYQrPKmNhSAn0QqxjSdXlyqkZH1FK5ca7ZpxeXf4qOzZtC5qbNFEW/SMTSGxtAYGkNjaAyNoTE0hsbQGPo/ghIJ9Y+sbvY/rVt/PKtbvcF7/tGlmzt26+YPiHs/eK9J6VOG9UqS0LEUpUaS0nJViAMzldJMUEjWsHYmCL1qULpdp5QvEXLiSnnudGj5AL4K4HmTMdWijCTxOg6o4wref7Vcuu4DPgrgFB683cLrNaB4xSYEcUMYIzcqTmn1uEMh9DgCPaCGQEzEBGbj+0SPme5uPPgRqVACbQANxMo4FfGIO2pS9nDTGQr+OIolF6RC5jtgATiNCZM9ZuYUVE0C+IDtKWRk5UQEU5Ywd1Aj9PtQ9fem0j2YMC5BzJdDedi0iLgahjwr1HxAXEu4fxypGvnCLfBIS2GpfVA2BnDGDxaN0bQ8pB2UY585sx9FHiGtXCjKFBL++oLgv3O/oRvBvgnlhsKPN2x+5PA9m7IhWTQW5W9C8hec2f1Rc6PiZZV3LsJKMoet+g2/Z/uttr0balP48DHYJC9tk25UuYVSWTyn5Pl47npLKIAdAE4DeL/l+ZBK/7ZscZlz5N5FDQ6gFtdCUAA7AcwBuDXoQ3mWiKhvMJ7AI+6lYfACwC8DPNOUUwDGjGggB+wUqrx/XvA3sOQSDxQF8bpNlUuwY0cASqfRMXXNW/JdYwM4COAH426hNuEWZxzBD0JZCIxWvaIT9mMTFEFD2G1OgsIbWZb7xEkAhwFcPYrDbiR9LMEOv1u0iboQhy+Xi7+FCrXHsCl2qRPw59vo5WFY6vR4o/UifNyPuGEovAXgmxfdgtPSUntSdhq7zYPYHG5fKhedVpV/zrDVFKMPYck/QeGv6+38TH4+b1h0rZ1fjmd0S4mK+0/+R/0lwAD2yuPPDbbSegAAAABJRU5ErkJggg%3D%3D); background-repeat: no-repeat;}")); 73 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox .CMY_RB_LinkCounts{text-align: center; background: #111; font-size: 11px; font-weight: bold; padding: 0px 0px 10px 0px; box-sizing: border-box; color: #fff; line-height: 12px; width: 50%;}")); 74 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox #CMY_RB_LC_Left{float: left;}")); 75 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox #CMY_RB_LC_Right{float: right; color: #f59703;}")); 76 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox #CMY_RB_Perc{font-size: 50px; line-height: 50px; color:#fff; background: #111; padding: 10px; text-shadow: 0px 0px 5px rgba(255,255,255,0.8); text-align: center;}")); 77 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox #CMY_RB_Pass{border-top: 1px solid #bddf5d; border-right: 1px solid #8db615; float:left; border-radius: 0px 0px 0px 5px; background-color:#9fcd19; background-position-x: 8px; background-position-y: 5px;}")); 78 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox #CMY_RB_Fail{border-top: 1px solid #c54f4f; float: right; border-radius: 0px 0px 5px 0px; background-color: #a9060a; background-position-x: 8px; background-position-y: -52px;}")); 79 | reportStyle.appendChild(document.createTextNode("#CMY_ReportBox #CMY_RB_Close{float: right;color:#ffffff;cursor:pointer;}")); 80 | 81 | var reportBox = document.createElement("div"); 82 | var rbHeader = document.createElement("div"); 83 | var rbPerc = document.createElement("div"); 84 | var rbAmt = document.createElement("div"); 85 | var rbQueue = document.createElement("div"); 86 | var rbPass = document.createElement("div"); 87 | var rbFail = document.createElement("div"); 88 | var rbClose = document.createElement("div"); 89 | rbClose.innerHTML = "X"; 90 | rbClose.setAttribute("id", "CMY_RB_Close"); 91 | 92 | reportBox.setAttribute("id", "CMY_ReportBox"); 93 | rbHeader.setAttribute("id", "CMY_RB_Header"); 94 | rbHeader.innerHTML = "Link Results"; 95 | rbPerc.setAttribute("id", "CMY_RB_Perc"); 96 | 97 | rbAmt.setAttribute("id", "CMY_RB_LC_Left"); 98 | rbAmt.setAttribute("class", "CMY_RB_LinkCounts"); 99 | 100 | rbQueue.setAttribute("id", "CMY_RB_LC_Right"); 101 | rbQueue.setAttribute("class", "CMY_RB_LinkCounts"); 102 | 103 | rbPass.setAttribute("id", "CMY_RB_Pass"); 104 | rbPass.setAttribute("class", "CMY_RB_ResultCount"); 105 | rbFail.setAttribute("id", "CMY_RB_Fail"); 106 | rbFail.setAttribute("class", "CMY_RB_ResultCount"); 107 | 108 | document.getElementsByTagName("body")[0].appendChild(reportBox); 109 | rpBox = document.getElementById("CMY_ReportBox"); 110 | 111 | rbHeader.appendChild(rbClose); 112 | rpBox.appendChild(rbHeader); 113 | rpBox.appendChild(rbPerc); 114 | rpBox.appendChild(rbAmt); 115 | rpBox.appendChild(rbQueue); 116 | rpBox.appendChild(rbPass); 117 | rpBox.appendChild(rbFail); 118 | 119 | 120 | rpBoxPerc = document.getElementById("CMY_RB_Perc"); 121 | rpBoxAmt = document.getElementById("CMY_RB_LC_Left"); 122 | rpBoxQueue = document.getElementById("CMY_RB_LC_Right"); 123 | rpBoxPass = document.getElementById("CMY_RB_Pass"); 124 | rpBoxFail = document.getElementById("CMY_RB_Fail"); 125 | 126 | rpBoxPerc.innerHTML = "0%"; 127 | rpBoxAmt.innerHTML = "Links: 0"; 128 | rpBoxQueue.innerHTML = "Queue: 0"; 129 | rpBoxPass.innerHTML = "0"; 130 | rpBoxFail.innerHTML = "0"; 131 | 132 | // Run through links, add feedback classes and ignore empty href and non http* links 133 | for (var i = 0; i < pg.length; i++){ 134 | 135 | var link = pg[i]; 136 | var url = link.href; 137 | var rel = link.rel; 138 | blacklisted = false; 139 | if ((url.length <= 0) || (request.nf=='false' && rel == "nofollow") || (url.startsWith('http')===false)){ 140 | totalvalid -=1; 141 | } 142 | else{ 143 | for (var b = 0; b < blacklist.length; b++) 144 | { 145 | if (blacklist[b] !== "" && url.contains(blacklist[b])){ 146 | blacklisted = true; 147 | } 148 | } 149 | 150 | if (blacklisted === true){ 151 | console.log("Skipped (blacklisted): " + url); 152 | totalvalid -=1; 153 | } 154 | else{ 155 | if(url.indexOf("#")==url.length-1){ 156 | totalvalid -=1; 157 | } 158 | else{ 159 | queued +=1; 160 | link.classList.add("CMY_Link"); 161 | checkURL(url, link); 162 | } 163 | 164 | } 165 | } 166 | } 167 | 168 | rpBoxAmt.innerHTML = "Links: " + totalvalid; 169 | // When close element is clicked, hide UI 170 | document.getElementById("CMY_RB_Close").onclick=function(){removeDOMElement("CMY_ReportBox");}; 171 | // Remove the event listener in the event this is run again without reloading 172 | chrome.extension.onMessage.removeListener(doStuff); 173 | }(pageLinks) 174 | ) 175 | 176 | // Send links to get checked via XHR 177 | function checkURL(url, link) { 178 | chrome.extension.sendMessage({"action": "check", "url": url}, 179 | function (response) { 180 | updateDisplay(url,link,response); 181 | }); 182 | } 183 | 184 | function updateDisplay(url,link,response){ 185 | if (response) { 186 | if (200 <= response && response < 400) { 187 | link.classList.add("CMY_Valid"); 188 | passed +=1; 189 | rpBoxPass.innerHTML = passed; 190 | } 191 | else { 192 | console.log("Response " + response + ": " + url); 193 | link.classList.add("CMY_Invalid"); 194 | link.innerHTML += " " + response + ""; 195 | invalid +=1; 196 | rpBoxFail.innerHTML = invalid; 197 | } 198 | queued -=1; 199 | checked +=1; 200 | rpBoxPerc.innerHTML = Math.floor((checked)/totalvalid * 100) + "%"; 201 | rpBoxQueue.innerHTML = "Queue: " + queued; 202 | } 203 | } 204 | return true; 205 | 206 | }); 207 | 208 | 209 | --------------------------------------------------------------------------------