├── README.md ├── background.js ├── images ├── icon128.png ├── icon16.png ├── icon19.png └── icon48.png ├── loadcss.js ├── manifest.json ├── options.css ├── options.html └── options.js /README.md: -------------------------------------------------------------------------------- 1 | # CSS Inject 2 | 3 |  4 | A Chrome Extension that allows injection of any hosted CSS file into any webpage 5 | 6 | ## Installation 7 | 8 | Just download and install it like any other old chrome extension. 9 | 10 | [Download Available in Chrome Extension Gallery](https://chrome.google.com/extensions/detail/fmiohbdblcemacakpnoinjmcelddpjbg) 11 | 12 | 13 | ## Features 14 | 15 | * Specify any hosted CSS file to inject (local files must be hosted under http://localhost/ - security restriction of Chrome) 16 | * Single click toggles injection on/off 17 | * Native JavaScript only - no included libraries or unnecessary code to slow you down. 18 | 19 | ## Why do I want this? 20 | 21 | This is to aid in theming websites such as Wordpress or Tumblr. 22 | It's for those cases where you're developing a complex Stylesheet for a site template 23 | but you can't easily update the CSS file on the server. 24 | 25 | Using this plug-in: you can work off a localhost hosted CSS file, just hit save, and see the results immediately. 26 | No more copying and pasting, or FTP-ing your CSS up to the server. 27 | 28 | Other CSS manipulation plugins only inject their own static css, or make you copy and paste into a box. 29 | 30 | I'm sure there's some other use cases too. If you have any, let me know! 31 | 32 | ## Boring Copyright Stuff 33 | 34 | Copyright (C) 2011 by Ed Rooth (www.edrooth.com) 35 | 36 | Permission is hereby granted, free of charge, to any person obtaining a copy 37 | of this software and associated documentation files (the "Software"), to deal 38 | in the Software without restriction, including without limitation the rights 39 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 40 | copies of the Software, and to permit persons to whom the Software is 41 | furnished to do so, subject to the following conditions: 42 | 43 | The above copyright notice and this permission notice shall be included in 44 | all copies or substantial portions of the Software. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 48 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 52 | THE SOFTWARE. 53 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | /*global chrome: true*/ 2 | 3 | (function () { 4 | 'use strict'; 5 | 6 | // TODO: Shortcut keys 7 | // TODO: Optionally remove ALL existing CSS? 8 | 9 | // global object to track on/off state of each tab 10 | var state = {}, 11 | storage = window.localStorage; 12 | 13 | // Displays error messages 14 | function error(msg) { 15 | window.alert('ERROR: ' + msg); 16 | } 17 | 18 | // Checks if the supplied url is valid 19 | // TODO: make this more robust 20 | function isUrlValid(url) { 21 | return (url && url.length > 3) ? true : false; 22 | } 23 | 24 | // send 'load/unload' request to the embedded content script 25 | function sendInjectionRequest(tabId, tabState, callback) { 26 | chrome.tabs.sendRequest( 27 | tabId, 28 | { 29 | // state of current tab 30 | state: tabState, 31 | // id of element to inject 32 | id: 'cssinject-injected-cssfile', 33 | // css file path specified by user in options plus a 34 | // timestamp cache buster. 35 | href: storage.cssfile + '?' + (new Date()).getTime() 36 | }, 37 | function(resp) { 38 | // something went wrong 39 | if (!resp.ok) { 40 | error('Could not load CSS file'); 41 | } 42 | if (callback) { 43 | callback(); 44 | } 45 | } 46 | ); 47 | } 48 | 49 | // Turn on the plugin badge and inject the css 50 | function turnOn(tabId) { 51 | // error check 52 | if (!isUrlValid(storage.cssfile)) { 53 | error('No CSS url specified. Please specify url in extesion options.'); 54 | return; 55 | } 56 | // update state 57 | state[tabId] = 'on'; 58 | // send request to content script 59 | sendInjectionRequest(tabId, state[tabId], function() { 60 | // update badge 61 | chrome.browserAction.setBadgeText({text: 'on', tabId: tabId}); 62 | }); 63 | } 64 | 65 | // Turn off the plugin badge 66 | function turnOff(tabId) { 67 | // just delete the property if it exists 68 | delete state[tabId]; 69 | // send request to content script 70 | sendInjectionRequest(tabId, 'off', function() { 71 | // update badge 72 | chrome.browserAction.setBadgeText({text: '', tabId: tabId}); 73 | }); 74 | } 75 | 76 | // toggles css injection on/off 77 | function toggleInjection(tab) { 78 | // toggle state on click 79 | if (state[tab.id] === 'on') { 80 | turnOff(tab.id); 81 | } else { 82 | turnOn(tab.id); 83 | } 84 | } 85 | 86 | // restore state on page reloads 87 | function restoreState(req, sender, sendResponse) { 88 | // first get current window 89 | chrome.windows.getCurrent(function(win) { 90 | // then get current tab 91 | chrome.tabs.getSelected(win.id, function(tab) { 92 | // check the tab's state 93 | if (state[tab.id] === 'on') { 94 | // if it should be on turn it on 95 | turnOn(tab.id); 96 | // notify content script that all is good 97 | sendResponse({ok: true}); 98 | } 99 | }); 100 | }); 101 | } 102 | 103 | // EVENT HANDLERS 104 | 105 | // User clicked the activate action button, 106 | // kick off all the injection goodness. 107 | chrome.browserAction.onClicked.addListener(toggleInjection); 108 | // Handle requests from embedded content script. 109 | chrome.extension.onRequest.addListener(restoreState); 110 | }()); 111 | -------------------------------------------------------------------------------- /images/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sym3tri/CSS-Inject/348ed02782a5316f99969a545874f522c848dbca/images/icon128.png -------------------------------------------------------------------------------- /images/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sym3tri/CSS-Inject/348ed02782a5316f99969a545874f522c848dbca/images/icon16.png -------------------------------------------------------------------------------- /images/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sym3tri/CSS-Inject/348ed02782a5316f99969a545874f522c848dbca/images/icon19.png -------------------------------------------------------------------------------- /images/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sym3tri/CSS-Inject/348ed02782a5316f99969a545874f522c848dbca/images/icon48.png -------------------------------------------------------------------------------- /loadcss.js: -------------------------------------------------------------------------------- 1 | // All DOM manipulation must exist inside this "content script" in order to execute in the proper context of the page 2 | 3 | // inject the css file into the head element 4 | function appendStyleNode(id, href) { 5 | var cssNode = document.createElement('link'); 6 | cssNode.type = 'text/css'; 7 | cssNode.rel = 'stylesheet'; 8 | cssNode.id = id; 9 | cssNode.href = href; 10 | document.getElementsByTagName('head')[0].appendChild(cssNode); 11 | } 12 | 13 | // removes the css 14 | function removeStyleNode(id) { 15 | var node = document.getElementById(id); 16 | node && node.parentNode.removeChild(node); 17 | } 18 | 19 | // currently does nothing but alert if error 20 | function restoreStateCallback(resp) { 21 | if (!resp.ok) { 22 | alert('Error re-injecting css on refresh. Try pushing the button again'); 23 | } 24 | } 25 | 26 | 27 | // EVENT LISTENERS 28 | 29 | // override window onload event to check the state of the current tab on each page load 30 | var oldWindowOnload = window.onload; 31 | window.onload = function() { 32 | 33 | // send request to background page to restore the state (since only it knows about state) 34 | chrome.extension.sendRequest({action: 'restoreState'}, restoreStateCallback); 35 | 36 | // execute any previously existing window onload events 37 | if (oldWindowOnload && typeof(oldWindowOnload) === 'function') { 38 | oldWindowOnload(); 39 | } 40 | }; 41 | 42 | // listen to injections/removal requests from background.html 43 | chrome.extension.onRequest.addListener( 44 | 45 | // depending on state value, injext/remove css 46 | function(req, sender, sendResponse) { 47 | req.state === 'on' 48 | ? appendStyleNode(req.id, req.href) 49 | : removeStyleNode(req.id); 50 | 51 | // notify of no problems 52 | sendResponse({ok: true}); 53 | } 54 | ); 55 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "CSS Inject", 4 | "version": "1.2.3", 5 | "description": "Injects any hosted CSS file into a any webpage", 6 | "homepage_url": "https://github.com/sym3tri/CSS-Inject", 7 | 8 | "browser_action": { 9 | "default_title": "CSS Inject", 10 | "default_icon": "images/icon19.png" 11 | }, 12 | 13 | "icons": { 14 | "16": "images/icon16.png", 15 | "48": "images/icon48.png", 16 | "128": "images/icon128.png" 17 | }, 18 | 19 | "options_page": "options.html", 20 | 21 | "background": { 22 | "scripts": ["background.js"] 23 | }, 24 | 25 | "content_scripts": [{ 26 | "matches": ["http://*/*", "https://*/*"], 27 | "js": ["loadcss.js"] 28 | }], 29 | 30 | "permissions": [ 31 | "tabs", "http://*/*", "https://*/*" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /options.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: arial, sans-serif; 3 | font-size: 14px; 4 | background-color: #efefef; 5 | } 6 | #header { 7 | clear: both; 8 | width: 300px; 9 | font-family: arial; 10 | } 11 | #logo { 12 | float: left; 13 | } 14 | #header h1 { 15 | color: #333; 16 | float: right; 17 | margin-top: 50px; 18 | } 19 | #content { 20 | clear: both; 21 | } 22 | #status { 23 | height: 50px; 24 | color: limeGreen; 25 | } 26 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |