├── .gitignore ├── Chrome └── LiveReload │ ├── IconActive.png │ ├── IconActive@2x.png │ ├── IconDisabled.png │ ├── IconDisabled@2x.png │ ├── IconEnabled.png │ ├── IconEnabled@2x.png │ ├── IconUnavailable.png │ ├── IconUnavailable@2x.png │ ├── devtools.html │ ├── icon128.png │ ├── icon48.png │ └── manifest.json ├── Firefox ├── chrome.manifest ├── content │ └── browser.xul ├── defaults │ └── preferences │ │ └── defaults.js ├── install.rdf └── skin │ ├── IconActive.png │ ├── IconDisabled.png │ ├── IconEnabled.png │ ├── IconUnavailable.png │ └── icon_32.png ├── Gruntfile.js ├── LiveReload.safariextension ├── Icon-32.png ├── Icon-48.png ├── IconActive.png ├── IconActive@2x.png ├── IconDisabled.png ├── IconDisabled@2x.png ├── IconEnabled.png ├── IconEnabled@2x.png ├── IconUnavailable.png ├── IconUnavailable@2x.png ├── Info.plist ├── Settings.plist └── global.html ├── Opera ├── config.xml ├── images │ ├── IconActive.png │ ├── IconDisabled.png │ ├── IconEnabled.png │ ├── IconUnavailable.png │ └── livereload_64.png ├── includes │ ├── .DS_Store │ └── injected.js ├── index.html └── scripts │ ├── .DS_Store │ ├── background.js │ └── o-min.js ├── README.md ├── Rakefile ├── dist └── .keepme ├── package.json ├── src ├── chrome │ ├── devtools.coffee │ ├── global.coffee │ └── injected.coffee ├── common │ ├── devtools.coffee │ ├── global.coffee │ ├── injected.coffee │ └── version.coffee ├── firefox │ └── firefox.coffee ├── livereload-js.coffee └── safari │ ├── global.coffee │ └── injected.coffee └── update ├── LiveReload-Firefox-update.rdf └── LiveReload-Safari-update.plist /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | LiveReload.safariextension/*.js 3 | Chrome/LiveReload/*.js 4 | Firefox/content/*.js 5 | lib/*.js 6 | lib/*/*.js 7 | /dist 8 | /update/McCoy 9 | /Chrome/*.pem 10 | -------------------------------------------------------------------------------- /Chrome/LiveReload/IconActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconActive.png -------------------------------------------------------------------------------- /Chrome/LiveReload/IconActive@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconActive@2x.png -------------------------------------------------------------------------------- /Chrome/LiveReload/IconDisabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconDisabled.png -------------------------------------------------------------------------------- /Chrome/LiveReload/IconDisabled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconDisabled@2x.png -------------------------------------------------------------------------------- /Chrome/LiveReload/IconEnabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconEnabled.png -------------------------------------------------------------------------------- /Chrome/LiveReload/IconEnabled@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconEnabled@2x.png -------------------------------------------------------------------------------- /Chrome/LiveReload/IconUnavailable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconUnavailable.png -------------------------------------------------------------------------------- /Chrome/LiveReload/IconUnavailable@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/IconUnavailable@2x.png -------------------------------------------------------------------------------- /Chrome/LiveReload/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chrome/LiveReload/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/icon128.png -------------------------------------------------------------------------------- /Chrome/LiveReload/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Chrome/LiveReload/icon48.png -------------------------------------------------------------------------------- /Chrome/LiveReload/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "LiveReload", 4 | "version": "2.1.0", 5 | "background": { 6 | "scripts": [ 7 | "global.js" 8 | ] 9 | }, 10 | "content_scripts": [ 11 | { 12 | "matches": [""], 13 | "js": ["injected.js"] 14 | } 15 | ], 16 | "web_accessible_resources": [ 17 | "livereload.js" 18 | ], 19 | "permissions": [ 20 | "tabs", 21 | "" 22 | ], 23 | "icons": { "48": "icon48.png", 24 | "128": "icon128.png" }, 25 | "browser_action": { 26 | "default_title": "Enable LiveReload", 27 | "default_icon": "IconDisabled.png" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Firefox/chrome.manifest: -------------------------------------------------------------------------------- 1 | content livereload content/ contentaccessible=yes 2 | skin livereload classic/1.0 skin/ 3 | 4 | overlay chrome://browser/content/browser.xul chrome://livereload/content/browser.xul 5 | -------------------------------------------------------------------------------- /Firefox/content/browser.xul: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Opera/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | LiveReload 2.0.1 (Beta) 4 | LiveReload refreshes a web page when files change. Please make sure you enable Websockets and LiveReload is running. 5 | [Beta note] This extension is not as sophisticated as the Chrome/Firefox version; keep your bug reports coming and it might get there. 6 | Gabrijel Gavranović 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Opera/images/IconActive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Opera/images/IconActive.png -------------------------------------------------------------------------------- /Opera/images/IconDisabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Opera/images/IconDisabled.png -------------------------------------------------------------------------------- /Opera/images/IconEnabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Opera/images/IconEnabled.png -------------------------------------------------------------------------------- /Opera/images/IconUnavailable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Opera/images/IconUnavailable.png -------------------------------------------------------------------------------- /Opera/images/livereload_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Opera/images/livereload_64.png -------------------------------------------------------------------------------- /Opera/includes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Opera/includes/.DS_Store -------------------------------------------------------------------------------- /Opera/includes/injected.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Author: Gabrijel Gavranović // gavro.nl 3 | * CustomEvent & LiveReload objects/prototype function taken (and partially adapted) from: 4 | * LiveReload Chrome & Firefox extentions, LiveReload.com & Nikita Vasilyev. 5 | * See: http://help.livereload.com/kb/general-use/browser-extensions 6 | ***/ 7 | 8 | var tabId, CustomEvents, ExtVersion, LiveReloadInjected, liveReloadInjected; 9 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 10 | CustomEvents = { 11 | bind: function(element, eventName, handler) { 12 | if (element.addEventListener) { 13 | return element.addEventListener(eventName, handler, false); 14 | } else if (element.attachEvent) { 15 | element[eventName] = 1; 16 | return element.attachEvent('onpropertychange', function(event) { 17 | if (event.propertyName === eventName) { 18 | return handler(); 19 | } 20 | }); 21 | } else { 22 | throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement"); 23 | } 24 | }, 25 | fire: function(element, eventName) { 26 | var document, event; 27 | document = element instanceof window.HTMLDocument ? element : element.ownerDocument; 28 | if (element.addEventListener) { 29 | event = document.createEvent('HTMLEvents'); 30 | event.initEvent(eventName, true, true); 31 | return document.dispatchEvent(event); 32 | } else if (element.attachEvent) { 33 | if (element[eventName]) { 34 | return element[eventName]++; 35 | } 36 | } else { 37 | throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement"); 38 | } 39 | } 40 | }; 41 | ExtVersion = '2.0.1'; 42 | LiveReloadInjected = (function() { 43 | function LiveReloadInjected(tabId, document, extName) { 44 | this.tabId = tabId; 45 | this.document = document; 46 | this.extName = extName; 47 | }; 48 | LiveReloadInjected.prototype.doDisable = function() { 49 | var element = this.document.getElementById('lr-script'); 50 | if (element) { 51 | CustomEvents.fire(this.document, 'LiveReloadShutDown'); 52 | if (element.parentNode) { 53 | element.parentNode.removeChild(element); 54 | } 55 | } 56 | return; 57 | }; 58 | LiveReloadInjected.prototype.doEnable = function(_arg) { 59 | var element, scriptURI, url, useFallback; 60 | useFallback = _arg.useFallback, scriptURI = _arg.scriptURI; 61 | if (useFallback) { 62 | url = "" + scriptURI + "?ext=" + this.extName + "&extver=" + ExtVersion + "&host=localhost"; 63 | console.log("Loading LiveReload.js bundled with the browser extension..."); 64 | } else { 65 | url = "http://localhost:35729/livereload.js?ext=" + this.extName + "&extver=" + ExtVersion; 66 | console.log("Loading LiveReload.js from " + (url.replace(/\?.*$/, '')) + "..."); 67 | } 68 | element = this.document.createElement('script'); 69 | element.src = url; 70 | element.id = "lr-script"; 71 | return this.document.getElementsByTagName('head')[0].appendChild(element); 72 | }; 73 | LiveReloadInjected.prototype.disable = function() { 74 | return this.doDisable(__bind(function() { 75 | return this.send('status', { 76 | enabled: false, 77 | active: false 78 | }); 79 | }, this)); 80 | }; 81 | LiveReloadInjected.prototype.enable = function(options) { 82 | return this.doDisable(__bind(function() { 83 | this.doEnable(options); 84 | return this.send('status', { 85 | enabled: true 86 | }); 87 | }, this)); 88 | }; 89 | return LiveReloadInjected; 90 | })(); 91 | 92 | 93 | 94 | window.addEventListener('DOMContentLoaded', function(event) { 95 | /*** 96 | * We need a sort of handshake communication: need to check and/or set current tab ID for things to work. 97 | * Opera currently does not support an "onReload" function and neither is it possible to get the current tab id 98 | * from the injected JS. From background.js it is not possible to any useful information (e.g. the tab ID) from 99 | * the connecting tab if it is not the focused tab (event object has limited data and tab/windows extension 100 | * functionality is far from complete)! 101 | * LiveReload reloads the entire tab if the page src (html) is edited, so background functionality is really needed. 102 | * 103 | * Current workflow: (work-around), with added latency...): 104 | * > [inject.js] send message on DOM ready with seesiondata: tabId (instead of onconnect @ background.js) 105 | * > [background.js] is tabId null or set? If set: resume state from own stored data. Null? New setup. 106 | ***/ 107 | tabId = window.sessionStorage.getItem('tabId'); 108 | opera.extension.postMessage({action: 'initConnection', tabid: tabId}); 109 | 110 | opera.extension.onmessage = function(event){ 111 | if(tabId == event.data.tabid || event.data.action.indexOf('Connection') > -1) { 112 | switch(event.data.action) { 113 | case 'setupConnection': 114 | //store your own ID, used for further communications (broadcastMessage is multicast only... :/) 115 | tabId = event.data.tabid; 116 | window.sessionStorage.setItem('tabId', event.data.tabid); 117 | liveReloadInjected = new LiveReloadInjected(event.data.tabid, document, 'Opera'); 118 | break; 119 | case 'resumeConnection': 120 | if(event.data.state === 'active') { 121 | liveReloadInjected = new LiveReloadInjected(event.data.tabid, document, 'Opera'); 122 | liveReloadInjected.doEnable({useFallback: false, scriptURI: ''}); 123 | } else { 124 | liveReloadInjected = new LiveReloadInjected(event.data.tabid, document, 'Opera'); 125 | } 126 | break; 127 | case 'enable': 128 | liveReloadInjected.doEnable({useFallback: false, scriptURI: ''}); 129 | //can't call internal scripts (getFile function from extension does not exsist) @ Opera Extension 130 | //(or I just don't know how...) 131 | break; 132 | case 'disable': 133 | liveReloadInjected.doDisable(); 134 | break; 135 | } 136 | } 137 | } 138 | }, false); -------------------------------------------------------------------------------- /Opera/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Opera/scripts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/Opera/scripts/.DS_Store -------------------------------------------------------------------------------- /Opera/scripts/background.js: -------------------------------------------------------------------------------- 1 | /*** 2 | * Author: Gabrijel Gavranović // gavro.nl 3 | * CustomEvent & LiveReload objects/prototype function taken (and partially adapted) from: 4 | * LiveReload Chrome & Firefox extentions, LiveReload.com & Nikita Vasilyev. 5 | * See: http://help.livereload.com/kb/general-use/browser-extensions 6 | ***/ 7 | 8 | //global vars/objects 9 | /*** 10 | * lrdata structure: tabID, status --> We should keep track of closed tab and remove them but can't get the ID 11 | * of the closed tabs! So let the object fill up, better a larger object instead of a periodical check of 12 | * object agains all open tabs with extension.tabs.getAll().... 13 | ***/ 14 | var lrdata = {}; 15 | 16 | window.addEventListener("load", setupConnection, false); 17 | 18 | function setupConnection() { 19 | var UIItemProperties = { 20 | disabled: true, 21 | title: "Please reload the page to be able to enable the LiveReload plugin.", 22 | icon: "images/IconDisabled.png", 23 | onclick: function(){ 24 | toggleLiveReload(true); 25 | } 26 | }; 27 | 28 | toggleButton = opera.contexts.toolbar.createItem(UIItemProperties); 29 | opera.contexts.toolbar.addItem(toggleButton); 30 | 31 | /*** 32 | * We need a sort of handshake communication: need to check and/or set current tab ID for things to work. 33 | * Opera currently does not support an "onReload" function and neither is it possible to get the current tab id 34 | * from the injected JS. From background.js it is not possible to any useful information (e.g. the tab ID) from 35 | * the connecting tab if it is not the focused tab (event object has limited data and tab/windows extension 36 | * functionality is far from complete)! 37 | * LiveReload reloads the entire tab if the page src (html) is edited, so background functionality is really needed. 38 | * 39 | * Current workflow: (work-around), with added latency...): 40 | * > [inject.js] send message on DOM ready with seesiondata: tabId (instead of onconnect @ background.js) 41 | * > [background.js] is tabId null or set? If set: resume state from own stored data. Null? New setup. 42 | ***/ 43 | 44 | /*opera.extension.onconnect = function(event) { 45 | var tab = opera.extension.tabs.getFocused(); 46 | if(tab) { 47 | if(o.has(lrdata, tab.id) && lrdata[tab.id].state === 'active') { 48 | event.source.postMessage({action: 'resumeConnection', tabid: tab.id}); 49 | } else { 50 | o.add(lrdata, tab.id, {state: 'inactive', timeStamp: event.timeStamp}); 51 | event.source.postMessage({action: 'setupConnection', tabid: tab.id}); 52 | 53 | toggleButton.disabled = false; 54 | toggleButton.title = 'Enable LiveReload'; 55 | } 56 | } 57 | }*/ 58 | 59 | opera.extension.onmessage = function(event) { 60 | switch(event.data.action) { 61 | case "initConnection": 62 | if(event.data.tabid === null) { 63 | var tab = opera.extension.tabs.getFocused(); 64 | o.add(lrdata, tab.id, {state: 'inactive'}); 65 | event.source.postMessage({action: 'setupConnection', tabid: tab.id}); 66 | toggleButton.disabled = false; 67 | toggleButton.title = 'Enable LiveReload'; 68 | } else if(o.has(lrdata, event.data.tabid) && lrdata[event.data.tabid].state === 'active'){ 69 | event.source.postMessage({action: 'resumeConnection', tabid: event.data.tabid, state: 'active'}); 70 | } else { 71 | //event.source.postMessage({action: 'resumeConnection', tabid: event.data.tabid, state: 'inactive'}); 72 | event.source.postMessage({action: 'setupConnection', tabid: event.data.tabid}); 73 | } 74 | break; 75 | } 76 | } 77 | 78 | opera.extension.tabs.onfocus = function(event) { 79 | var tab = opera.extension.tabs.getFocused(); 80 | if(tab) { 81 | if(!o.has(lrdata, tab.id)) { 82 | toggleButton.disabled = true; 83 | toggleButton.title = 'Please reload or even reopen the tab to be able to enable the LiveReload plugin.'; 84 | toggleButton.icon = 'images/IconDisabled.png'; 85 | } else { 86 | toggleButton.disabled = false; 87 | toggleLiveReload(event, false); 88 | } 89 | } 90 | } 91 | } 92 | 93 | 94 | function toggleLiveReload(change) { 95 | var tab = opera.extension.tabs.getFocused(); 96 | if(tab) { 97 | if(o.has(lrdata, tab.id)) { 98 | switch(lrdata[tab.id].state) { 99 | case 'inactive': 100 | if(change === true) { 101 | opera.extension.broadcastMessage({action: 'enable', tabid: tab.id}); 102 | lrdata[tab.id].state = 'active'; 103 | toggleButton.icon = 'images/IconActive.png'; 104 | } else { 105 | toggleButton.icon = 'images/IconDisabled.png'; 106 | } 107 | break; 108 | case 'active': 109 | if(change === true){ 110 | opera.extension.broadcastMessage({action: 'disable', tabid: tab.id}); 111 | lrdata[tab.id].state = 'inactive'; 112 | toggleButton.icon = 'images/IconDisabled.png'; 113 | } else { 114 | toggleButton.icon = 'images/IconActive.png'; 115 | } 116 | break; 117 | } 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /Opera/scripts/o-min.js: -------------------------------------------------------------------------------- 1 | //https://github.com/vladocar/o---JS-Library-for-Object-Manipulation 2 | (function(e){var d={add:function(a,b,c){return a[b]=c},remove:function(a,b){return delete a[b]},key:function(a,b){return a[b]},extend:function(a,b){for(var c in b)b.hasOwnProperty(c)&&(a[c]=b[c])},cloneAll:function(){for(var a={},b=arguments.length;b--;){var c=arguments[b],d;for(d in c)c.hasOwnProperty(d)&&(a[d]=c[d])}return a},values:function(a){var b=[],c;for(c in a)a.hasOwnProperty(c)&&b.push(a[c]);return b},keys:Object.keys||function(a){var b=[],c;for(c in a)a.hasOwnProperty(c)&&b.push(c);return b}, 3 | len:function(a){return this.keys(a).length},has:function(a,b){return a.hasOwnProperty(b)},isEmpty:function(a){return!this.len(a)},type:function(a,b){return b?Object.prototype.toString.call(a[b]):Object.prototype.toString.call(a)},is:function(a,b){return d.type(a)==="[object "+b+"]"},isArray:function(a){return d.is(a,"Array")},isObject:function(a){return d.is(a,"Object")},isRegExp:function(a){return d.is(a,"RegExp")},isFunction:function(a){return d.is(a,"Function")},isNumber:function(a){return d.is(a, 4 | "Number")},isString:function(a){return d.is(a,"String")},isBoolean:function(a){return d.is(a,"Boolean")},isNull:function(a){return d.is(a,"Null")},isUndefined:function(a){return d.is(a,"Undefined")},toJSON:function(a){return JSON.stringify(a)},toObject:function(a){return JSON.parse(a)},setStorage:function(a,b){return localStorage.setItem(a,JSON.stringify(b))},getStorage:function(a){return JSON.parse(localStorage.getItem(a))},removeStorage:function(a){return localStorage.removeItem(a)}};e.o=d})(window); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | LiveReload Browser Extensions 2 | ============================= 3 | 4 | Prerequsities: 5 | 6 | * Node.js (0.10.x or later) with npm 7 | 8 | Install dependencies: 9 | 10 | * `npm install` 11 | 12 | Build and package extensions: 13 | 14 | grunt chrome 15 | grunt firefox 16 | grunt all 17 | 18 | (Safari extension must be built manually, using Safari's GUI packager tool.) 19 | 20 | Build CoffeeScript modules, but don't pack: 21 | 22 | grunt build 23 | 24 | 25 | Release checklist 26 | ----------------- 27 | 28 | 1. Bump version number in `package.json`, run `rake version`, commit. 29 | 30 | 1. `grunt all` 31 | 32 | 1. Package Safari extension. 33 | 34 | 1. Test, test, test. 35 | 36 | 1. `rake tag` 37 | 38 | 1. `rake upload:safari`, `rake upload:firefox` or `rake upload:all` 39 | 40 | 1. Download and verify that uploads worked. 41 | 42 | 1. `rake upload:manifest` 43 | 44 | 1. Publish Chrome extension on the Chrome Web Store. 45 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | VERSION_FILES = %w( 2 | src/common/version.coffee 3 | LiveReload.safariextension/Info.plist 4 | Chrome/LiveReload/manifest.json 5 | Firefox/install.rdf 6 | ) 7 | 8 | def version 9 | content = File.read('package.json') 10 | if content =~ /"version": "(\d+\.\d+\.\d+)"/ 11 | return $1 12 | else 13 | raise "Failed to get version info from package.json" 14 | end 15 | end 16 | 17 | def subst_version_refs_in_file file, ver 18 | puts file 19 | orig = File.read(file) 20 | prev_line = "" 21 | anything_matched = false 22 | data = orig.lines.map do |line| 23 | if line =~ /\d\.\d\.\d/ && (line =~ /version/i || prev_line =~ /CFBundleShortVersionString|CFBundleVersion/) 24 | anything_matched = true 25 | new_line = line.gsub /\d\.\d\.\d/, ver 26 | puts " #{new_line.strip}" 27 | else 28 | new_line = line 29 | end 30 | prev_line = line 31 | new_line 32 | end.join('') 33 | 34 | raise "Error: no substitutions made in #{file}" unless anything_matched 35 | 36 | File.open(file, 'w') { |f| f.write data } 37 | end 38 | 39 | desc "Embed version number where it belongs" 40 | task :version do 41 | ver = version 42 | VERSION_FILES.each { |file| subst_version_refs_in_file(file, ver) } 43 | end 44 | 45 | 46 | def upload_file file, folder='dist' 47 | path = "#{folder}/#{file}" 48 | # application/x-chrome-extension 49 | sh 's3cmd', '-P', '--mime-type=application/octet-stream', 'put', path, "s3://download.livereload.com/#{file}" 50 | puts "http://download.livereload.com/#{file}" 51 | end 52 | 53 | desc "Upload the chosen build to S3" 54 | task 'upload:custom' do |t, args| 55 | require 'rubygems' 56 | require 'highline' 57 | HighLine.new.choose do |menu| 58 | menu.prompt = "Please choose a file to upload: " 59 | menu.choices(*Dir['dist/**/*.{crx,safariextz,xpi}'].sort.map { |f| f[5..-1] }) do |file| 60 | upload_file file 61 | end 62 | end 63 | end 64 | 65 | desc "Upload the latest Firefox build to S3" 66 | task 'upload:firefox' do 67 | upload_file "#{version}/LiveReload-#{version}.xpi" 68 | end 69 | 70 | desc "Upload the latest Safari build to S3" 71 | task 'upload:safari' do 72 | upload_file "#{version}/LiveReload-#{version}.safariextz" 73 | end 74 | 75 | desc "Upload the latest builds of all extensions to S3" 76 | task 'upload:all' => ['upload:safari', 'upload:firefox'] 77 | 78 | desc "Upload update manifests" 79 | task 'upload:manifest' do 80 | upload_file "LiveReload-Firefox-update.rdf", 'update' 81 | upload_file "LiveReload-Safari-update.plist", 'update' 82 | end 83 | 84 | desc "Tag the current version" 85 | task :tag do 86 | sh 'git', 'tag', "v#{version}" 87 | end 88 | desc "Move (git tag -f) the tag for the current version" 89 | task :retag do 90 | sh 'git', 'tag', '-f', "v#{version}" 91 | end 92 | -------------------------------------------------------------------------------- /dist/.keepme: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/livereload/livereload-extensions/0a8edb9cc7008249c23718386e19d99230e81179/dist/.keepme -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Andrey Tarantsov ", 3 | "name": "livereload-extensions", 4 | "description": "LiveReload browser extensions (not meant to be installed; npm is used for dependencies only)", 5 | "version": "2.1.0", 6 | "private": true, 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/livereload/livereload-extensions.git" 10 | }, 11 | "dependencies": { 12 | "livereload-js": "^2.2.1" 13 | }, 14 | "devDependencies": { 15 | "coffeeify": "^1.0.0", 16 | "grunt": "^0.4.5", 17 | "grunt-browserify": "^3.3.0", 18 | "grunt-contrib-coffee": "^0.12.0", 19 | "grunt-contrib-compress": "^0.13.0" 20 | }, 21 | "optionalDependencies": {}, 22 | "engines": { 23 | "node": "*" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/chrome/devtools.coffee: -------------------------------------------------------------------------------- 1 | require('../common/devtools') 2 | 3 | class DevTools 4 | 5 | resourceAdded: (resource) -> 6 | console.log "LiveReload.resourceAdded: #{resource.url}" 7 | @send 'resourceAdded', url: resource.url 8 | 9 | resourceUpdated: (resource, content) -> 10 | console.log "LiveReload.resourceUpdated: %s - %s", resource.url, content 11 | @send 'resourceUpdated', url: resource.url, content: content 12 | 13 | 14 | class ChromeDevTools extends DevTools 15 | 16 | send: (message, data) -> 17 | chrome.runtime.sendMessage [message, data] 18 | 19 | 20 | do -> 21 | 22 | devTools = new ChromeDevTools() 23 | 24 | chrome.devtools.inspectedWindow.onResourceAdded.addListener (resource) -> 25 | devTools.resourceAdded(resource) 26 | 27 | chrome.devtools.inspectedWindow.onResourceContentCommitted.addListener (resource, content) -> 28 | devTools.resourceUpdated(resource, content) 29 | -------------------------------------------------------------------------------- /src/chrome/global.coffee: -------------------------------------------------------------------------------- 1 | { LiveReloadGlobal, TabState } = require('../common/global') 2 | 3 | TabState::send = (message, data={}) -> 4 | chrome.tabs.sendMessage @tab, [message, data] 5 | 6 | TabState::bundledScriptURI = -> chrome.runtime.getURL('livereload.js') 7 | 8 | LiveReloadGlobal.isAvailable = (tab) -> yes 9 | 10 | LiveReloadGlobal.initialize() 11 | 12 | 13 | ToggleCommand = 14 | invoke: -> 15 | update: (tabId) -> 16 | status = LiveReloadGlobal.tabStatus(tabId) 17 | chrome.browserAction.setTitle { tabId, title: status.buttonToolTip } 18 | chrome.browserAction.setIcon { tabId, path: { '19' : status.buttonIcon, '38' : status.buttonIconHiRes } } 19 | 20 | 21 | chrome.browserAction.onClicked.addListener (tab) -> 22 | LiveReloadGlobal.toggle(tab.id) 23 | ToggleCommand.update(tab.id) 24 | 25 | chrome.tabs.onSelectionChanged.addListener (tabId, selectInfo) -> 26 | ToggleCommand.update(tabId) 27 | 28 | chrome.tabs.onRemoved.addListener (tabId) -> 29 | LiveReloadGlobal.killZombieTab tabId 30 | 31 | 32 | chrome.runtime.onMessage.addListener ([eventName, data], sender, sendResponse) -> 33 | # console.log "#{eventName}(#{JSON.stringify(data)})" 34 | switch eventName 35 | when 'status' 36 | LiveReloadGlobal.updateStatus(sender.tab.id, data) 37 | ToggleCommand.update(sender.tab.id) 38 | else 39 | LiveReloadGlobal.received(eventName, data) 40 | -------------------------------------------------------------------------------- /src/chrome/injected.coffee: -------------------------------------------------------------------------------- 1 | { LiveReloadInjected } = require('../common/injected') 2 | 3 | LiveReloadInjected::send = (message, data) -> 4 | chrome.runtime.sendMessage [message, data] 5 | 6 | liveReloadInjected = new LiveReloadInjected(document, window, 'Chrome') 7 | 8 | chrome.runtime.onMessage.addListener ([eventName, data], sender, sendResponse) -> 9 | # console.log "#{eventName}(#{JSON.stringify(data)})" 10 | switch eventName 11 | when 'alert' 12 | alert data 13 | when 'enable' 14 | liveReloadInjected.enable(data) 15 | when 'disable' 16 | liveReloadInjected.disable() 17 | -------------------------------------------------------------------------------- /src/common/devtools.coffee: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/common/global.coffee: -------------------------------------------------------------------------------- 1 | # LRClient = require 'livereload-client' 2 | 3 | ExtVersion = require('./version') 4 | 5 | Status = 6 | unavailable: 7 | buttonEnabled: no 8 | buttonToolTip: 'LiveReload not available on this tab' 9 | buttonIcon: 'IconUnavailable.png' 10 | buttonIconHiRes: 'IconUnavailable@2x.png' 11 | disabled: 12 | buttonEnabled: yes 13 | buttonToolTip: 'Enable LiveReload' 14 | buttonIcon: 'IconDisabled.png' 15 | buttonIconHiRes: 'IconDisabled@2x.png' 16 | enabled: 17 | buttonEnabled: yes 18 | buttonToolTip: 'LiveReload is connecting, click to disable' 19 | buttonIcon: 'IconEnabled.png' 20 | buttonIconHiRes: 'IconEnabled@2x.png' 21 | active: 22 | buttonEnabled: yes 23 | buttonToolTip: 'LiveReload is connected, click to disable' 24 | buttonIcon: 'IconActive.png' 25 | buttonIconHiRes: 'IconActive@2x.png' 26 | 27 | 28 | 29 | class TabState 30 | constructor: (@tab) -> 31 | @enabled = no 32 | @active = no 33 | 34 | enable: -> 35 | @send 'enable', { @useFallback, scriptURI: @bundledScriptURI(), host: LiveReloadGlobal.host, port: LiveReloadGlobal.port } 36 | 37 | disable: -> 38 | @send 'disable' 39 | 40 | updateStatus: (status) -> 41 | if status.initial 42 | if !status.enabled 43 | @active = no 44 | if @enabled 45 | @enable() 46 | return 47 | if status.enabled? 48 | @enabled = status.enabled 49 | if status.active? 50 | @active = status.active 51 | 52 | status: -> 53 | switch 54 | when @active 55 | Status.active 56 | when @enabled 57 | Status.enabled 58 | else 59 | Status.disabled 60 | 61 | alert: (message) -> 62 | @send 'alert', message 63 | 64 | 65 | if navigator.userAgent.match(/Mac OS X/) 66 | CannotConnectAlert = """Could not connect to LiveReload server. Please make sure that LiveReload 2.3 (or later) or another compatible server is running.""" 67 | else 68 | CannotConnectAlert = """Could not connect to LiveReload server. Please make sure that a compatible LiveReload server is running. (We recommend guard-livereload, until LiveReload 2 comes to your platform.)""" 69 | 70 | 71 | TheWebSocket = (WebSocket ? MozWebSocket) 72 | 73 | 74 | LiveReloadGlobal = 75 | _tabs: [] 76 | 77 | initialize: -> 78 | @host = '127.0.0.1' 79 | @port = 35729 80 | # @client = new LRClient 81 | # host: @host 82 | # port: @port 83 | # supportedProtocols: 84 | # monitoring: [LRClient.protocols.MONITORING_7] 85 | # connCheck: [LRClient.protocols.CONN_CHECK_1] 86 | # saving: [LRClient.protocols.SAVING_1] 87 | 88 | # WebSocket: TheWebSocket 89 | 90 | # id: 'com.livereload.extension.chrome' 91 | # name: 'Chrome extension' 92 | # version: ExtVersion 93 | # @client.open() 94 | 95 | 96 | killZombieTabs: -> 97 | @_tabs = (tabState for tabState in @_tabs when @isAvailable(tabState.tab)) 98 | 99 | killZombieTab: (tab) -> 100 | for tabState, index in @_tabs 101 | if tabState.tab is tab 102 | @_tabs.splice index, 1 103 | return 104 | return 105 | 106 | findState: (tab, create=no) -> 107 | for tabState in @_tabs 108 | return tabState if tabState.tab is tab 109 | if create 110 | state = new TabState(tab) 111 | @_tabs.push state 112 | state 113 | else 114 | null 115 | 116 | toggle: (tab) -> 117 | if @isAvailable(tab) 118 | state = @findState(tab, yes) 119 | if state.enabled 120 | state.disable() 121 | unless @areAnyTabsEnabled() 122 | @afterDisablingLast() 123 | else 124 | if @areAnyTabsEnabled() 125 | state.useFallback = @useFallback 126 | state.enable() 127 | else 128 | @beforeEnablingFirst (err) => 129 | if err 130 | switch err 131 | when 'cannot-connect' then state.alert(CannotConnectAlert) 132 | when 'cannot-download' then state.alert("Cannot download livereload.js") 133 | else 134 | state.useFallback = @useFallback 135 | state.enable() 136 | 137 | tabStatus: (tab) -> 138 | unless @isAvailable(tab) 139 | return Status.unavailable 140 | @findState(tab)?.status() || Status.disabled 141 | 142 | updateStatus: (tab, status) -> 143 | @findState(tab, yes).updateStatus(status) 144 | 145 | areAnyTabsEnabled: -> 146 | return yes for tabState in @_tabs when tabState.enabled 147 | no 148 | 149 | beforeEnablingFirst: (callback) -> 150 | @useFallback = no 151 | 152 | # probe using web sockets 153 | callbackCalled = no 154 | 155 | failOnTimeout = -> 156 | console.log "Haven't received a handshake reply in time, disconnecting." 157 | ws.close() 158 | timeout = setTimeout(failOnTimeout, 1000) 159 | 160 | console.log "Connecting to ws://#{@host}:#{@port}/livereload..." 161 | ws = new TheWebSocket("ws://#{@host}:#{@port}/livereload") 162 | ws.onerror = => 163 | console.log "Web socket error." 164 | callback('cannot-connect') unless callbackCalled 165 | callbackCalled = yes 166 | ws.onopen = => 167 | console.log "Web socket connected, sending handshake." 168 | ws.send JSON.stringify({ command: 'hello', protocols: ['http://livereload.com/protocols/connection-check-1'] }) 169 | ws.onclose = -> 170 | console.log "Web socket disconnected." 171 | callback('cannot-connect') unless callbackCalled 172 | callbackCalled = yes 173 | ws.onmessage = (event) => 174 | clearTimeout(timeout) if timeout 175 | timeout = null 176 | 177 | console.log "Incoming message: #{event.data}" 178 | if event.data.match(/^!!/) 179 | @useFallback = yes 180 | callback(null) unless callbackCalled 181 | callbackCalled = yes 182 | ws.close() 183 | else if event.data.match(/^\{/) 184 | xhr = new XMLHttpRequest() 185 | xhr.onreadystatechange = => 186 | if xhr.readyState is XMLHttpRequest.DONE and xhr.status is 200 187 | @script = xhr.responseText 188 | callback(null) unless callbackCalled 189 | callbackCalled = yes 190 | xhr.onerror = (event) => 191 | callback('cannot-download') unless callbackCalled 192 | callbackCalled = yes 193 | xhr.open("GET", "http://#{@host}:#{@port}/livereload.js", true) 194 | xhr.send(null) 195 | 196 | 197 | afterDisablingLast: -> 198 | 199 | 200 | received: (eventName, data) -> 201 | if func = @["on #{eventName}"] 202 | func.call(this, data) 203 | 204 | 'on resourceAdded': ({ url }) -> 205 | console.log "Resource added: #{url}" 206 | # if @client.connected 207 | # if @client.negotiatedProtocols?.connCheck >= 1 208 | # @client.send { command: "presave", url } 209 | # else 210 | # console.log "Saving protocol not supported." 211 | # else 212 | # @client.open() 213 | 214 | 'on resourceUpdated': ({ url, content }) -> 215 | console.log "Resource updated: #{url}" 216 | # if @client.connected 217 | # if @client.negotiatedProtocols?.connCheck >= 1 218 | # @client.send { command: "save", url, content } 219 | # else 220 | # console.log "Saving protocol not supported." 221 | # else 222 | # @client.open() 223 | 224 | 225 | exports.TabState = TabState 226 | exports.LiveReloadGlobal = LiveReloadGlobal 227 | -------------------------------------------------------------------------------- /src/common/injected.coffee: -------------------------------------------------------------------------------- 1 | CustomEvents = 2 | bind: (element, eventName, handler) -> 3 | if element.addEventListener 4 | element.addEventListener eventName, handler, false 5 | else if element.attachEvent 6 | element[eventName] = 1 7 | element.attachEvent 'onpropertychange', (event) -> 8 | if event.propertyName is eventName 9 | handler() 10 | else 11 | throw new Error("Attempt to attach custom event #{eventName} to something which isn't a DOMElement") 12 | 13 | fire: (element, eventName) -> 14 | document = if element instanceof HTMLDocument then element else element.ownerDocument 15 | if element.addEventListener 16 | event = document.createEvent('HTMLEvents') 17 | event.initEvent(eventName, true, true) 18 | document.dispatchEvent(event) 19 | else if element.attachEvent 20 | if element[eventName] 21 | element[eventName]++ 22 | else 23 | throw new Error("Attempt to fire custom event #{eventName} on something which isn't a DOMElement") 24 | 25 | ExtVersion = require('./version') 26 | 27 | class LiveReloadInjected 28 | 29 | constructor: (@document, @window, @extName) -> 30 | @_hooked = no 31 | @_verbose = !!@window?.location?.href?.match(/LR-verbose/) 32 | 33 | setTimeout((=> @determineInitialState()), 1) 34 | 35 | determineInitialState: -> 36 | if @findScriptTag() 37 | @send 'status', enabled: yes, active: yes, initial: yes 38 | @hook() 39 | else 40 | @send 'status', enabled: no, active: no, initial: yes 41 | 42 | findScriptTag: -> 43 | for element in @document.getElementsByTagName('script') 44 | if src = element.src 45 | if m = src.match /// /livereload\.js (?: \? (.*) )? $/// 46 | return element 47 | null 48 | 49 | doDisable: (callback) -> 50 | element = @findScriptTag() 51 | if element 52 | CustomEvents.fire @document, 'LiveReloadShutDown' 53 | element.parentNode.removeChild(element) if element.parentNode 54 | callback() 55 | 56 | doEnable: ({ useFallback, scriptURI, @host, @port })-> 57 | # our stuff isn't welcome in CKEditor's editing IFRAME :-) 58 | if @document.documentElement?.contentEditable is 'true' 59 | return 60 | 61 | if useFallback 62 | url = "#{scriptURI}?ext=#{@extName}&extver=#{ExtVersion}&host=#{@host}&port=#{@port}" 63 | if @_verbose 64 | console.log "Loading LiveReload.js bundled with the browser extension..." 65 | else 66 | url = "http://#{@host}:#{@port}/livereload.js?ext=#{@extName}&extver=#{ExtVersion}" 67 | if @_verbose 68 | console.log "Loading LiveReload.js from #{url.replace(/\?.*$/, '')}..." 69 | 70 | @hook() 71 | element = @document.createElement('script') 72 | element.src = url 73 | @document.body.appendChild(element) 74 | 75 | hook: -> 76 | return if @_hooked 77 | @_hooked = yes 78 | 79 | CustomEvents.bind @document, 'LiveReloadConnect', => 80 | @send 'status', active: yes 81 | CustomEvents.bind @document, 'LiveReloadDisconnect', => 82 | @send 'status', active: no 83 | 84 | disable: -> 85 | @doDisable => 86 | @send 'status', enabled: no, active: no 87 | 88 | enable: (options) -> 89 | @doDisable => 90 | @doEnable options 91 | @send 'status', enabled: yes 92 | 93 | exports.LiveReloadInjected = LiveReloadInjected 94 | -------------------------------------------------------------------------------- /src/common/version.coffee: -------------------------------------------------------------------------------- 1 | module.exports = ExtVersion = '2.1.0' 2 | -------------------------------------------------------------------------------- /src/firefox/firefox.coffee: -------------------------------------------------------------------------------- 1 | # Firefox does not use background/injected content separation, so this file 2 | # serves the purpose of both global-firefox and injected-firefox. 3 | { LiveReloadGlobal, TabState } = require('../common/global') 4 | { LiveReloadInjected } = require('../common/injected') 5 | 6 | findTabByContentDocument = (doc) -> 7 | for tab in gBrowser.tabs 8 | if gBrowser.getBrowserForTab(tab).contentDocument is doc 9 | return tab 10 | return null 11 | 12 | 13 | LiveReloadInjected::send = (eventName, data) -> 14 | tab = findTabByContentDocument(@document) 15 | unless tab 16 | # TODO: we're inside an (I)FRAME. Is any special treatment needed? 17 | return 18 | 19 | switch eventName 20 | when 'status' 21 | LiveReloadGlobal.updateStatus(tab, data) 22 | ToggleButton.update() 23 | 24 | TabState::send = (eventName, data={}) -> 25 | doc = gBrowser.getBrowserForTab(@tab).contentDocument 26 | injected = doc.__LiveReload_injected 27 | unless injected 28 | alert "There is no LiveReloadInjected for #{doc.location.href}" 29 | return 30 | 31 | switch eventName 32 | when 'alert' 33 | alert data 34 | when 'enable' 35 | injected.enable(data) 36 | when 'disable' 37 | injected.disable() 38 | 39 | TabState::bundledScriptURI = -> 'chrome://livereload/content/livereload.js' 40 | 41 | LiveReloadGlobal.isAvailable = (tab) -> yes 42 | 43 | LiveReloadGlobal.initialize() 44 | 45 | 46 | ToggleButton = 47 | initialize: -> 48 | @toggleButton = document.getElementById('livereload-button') 49 | @toggleButton.addEventListener 'command', (event) -> 50 | LiveReloadGlobal.toggle(gBrowser.selectedTab) 51 | ToggleButton.update() 52 | 53 | update: -> 54 | status = LiveReloadGlobal.tabStatus(gBrowser.selectedTab) 55 | @toggleButton.tooltiptext = status.buttonToolTip 56 | @toggleButton.image = "chrome://livereload/skin/#{status.buttonIcon}" 57 | 58 | 59 | window.addEventListener 'load', -> 60 | ToggleButton.initialize() 61 | 62 | # alert "Hello from LiveReload!" 63 | # event.view.gBrowser.selectedTab 64 | 65 | ContentScriptInjectionSimulation = 66 | initialize: -> 67 | gBrowser.addEventListener 'DOMContentLoaded', (event) -> 68 | doc = event.originalTarget 69 | win = doc.defaultView 70 | return if doc?.location?.href is 'about:blank' 71 | # alert "Page loaded! #{doc?.location?.href}" 72 | 73 | doc.__LiveReload_injected = new LiveReloadInjected(doc, win, 'Firefox') 74 | 75 | win.addEventListener "unload", (event) -> 76 | doc.__LiveReload_injected = null 77 | # alert "Page unloaded #{doc.foo?.x}: #{doc.location.href}" 78 | 79 | ContentScriptInjectionSimulation.initialize() 80 | 81 | # window.addEventListener "pagehide", (event) -> 82 | # if event.originalTarget instanceof HTMLDocument 83 | # doc = event.originalTarget 84 | 85 | gBrowser.tabContainer.addEventListener 'TabSelect', (event) -> 86 | tab = event.target 87 | ToggleButton.update() 88 | # alert "Tab select:\nlabel = #{tab.label}\ndocument = #{tab.linkedBrowser?.contentDocument}\ndocument.href = #{tab.linkedBrowser?.contentDocument?.location?.href}" 89 | # var index = livereloadBackground.pages.indexOf(tab); 90 | # if (index == -1) { 91 | # livereloadBackground.onDisablePage(tab); 92 | # } else { 93 | # livereloadBackground.onEnablePage(tab); 94 | # } 95 | # }, false); 96 | 97 | gBrowser.tabContainer.addEventListener 'TabClose', (event) -> 98 | LiveReloadGlobal.killZombieTab event.target 99 | ToggleButton.update() 100 | # tab = event.target 101 | # console.error "Tab close:" 102 | # console.error tab 103 | -------------------------------------------------------------------------------- /src/livereload-js.coffee: -------------------------------------------------------------------------------- 1 | require('livereload-js') 2 | -------------------------------------------------------------------------------- /src/safari/global.coffee: -------------------------------------------------------------------------------- 1 | { LiveReloadGlobal, TabState } = require('../common/global') 2 | 3 | TabState::send = (message, data={}) -> 4 | @tab.page.dispatchMessage message, data 5 | 6 | TabState::bundledScriptURI = -> safari.extension.baseURI + 'livereload.js' 7 | 8 | LiveReloadGlobal.isAvailable = (tab) -> !!tab.url 9 | 10 | LiveReloadGlobal.initialize() 11 | 12 | 13 | Commands = 14 | toggle: 15 | invoke: (event) -> 16 | LiveReloadGlobal.toggle(event.target.browserWindow.activeTab) 17 | event.target.validate() 18 | validate: (event) -> 19 | @toolbarItem = event.target 20 | LiveReloadGlobal.killZombieTabs() 21 | 22 | status = LiveReloadGlobal.tabStatus(event.target.browserWindow.activeTab) 23 | event.target.disabled = !status.buttonEnabled 24 | event.target.toolTip = status.buttonToolTip 25 | event.target.image = safari.extension.baseURI + status.buttonIcon 26 | 27 | revalidate: -> 28 | @toolbarItem?.validate() 29 | 30 | 31 | safari.application.addEventListener 'command', (event) -> 32 | Commands[event.command]?.invoke?(event) 33 | 34 | safari.application.addEventListener 'validate', (event) -> 35 | Commands[event.command]?.validate?(event) 36 | 37 | safari.application.addEventListener 'message', (event) -> 38 | # console.log "#{event.name}(#{JSON.stringify(event.message)})" 39 | switch event.name 40 | when 'status' 41 | LiveReloadGlobal.updateStatus(event.target, event.message) 42 | Commands.toggle.revalidate() 43 | -------------------------------------------------------------------------------- /src/safari/injected.coffee: -------------------------------------------------------------------------------- 1 | { LiveReloadInjected } = require('../common/injected') 2 | 3 | LiveReloadInjected::send = (message, data) -> 4 | safari.self.tab.dispatchMessage message, data 5 | 6 | liveReloadInjected = new LiveReloadInjected(document, window, 'Safari') 7 | 8 | safari.self.addEventListener 'message', (event) -> 9 | # console.log "#{event.name}(#{JSON.stringify(event.message)})" 10 | switch event.name 11 | when 'alert' 12 | alert event.message 13 | when 'enable' 14 | liveReloadInjected.enable(event.message) 15 | when 'disable' 16 | liveReloadInjected.disable() 17 | -------------------------------------------------------------------------------- /update/LiveReload-Firefox-update.rdf: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /update/LiveReload-Safari-update.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Extension Updates 6 | 7 | 8 | CFBundleIdentifier 9 | com.livereload.extensions.SafariLiveReload 10 | Developer Identifier 11 | D963M2VVCH 12 | CFBundleVersion 13 | 2.1.0 14 | CFBundleShortVersionString 15 | 2.1.0 16 | URL 17 | http://download.livereload.com/2.1.0/LiveReload-2.1.0.safariextz 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------