diff options
| author | Einar Egilsson <einar@einaregilsson.com> | 2018-08-17 10:41:58 +0000 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-08-17 10:41:58 +0000 | 
| commit | 506f78a13124fdbe5e33485dbd0b079cd8fffd47 (patch) | |
| tree | 0e269965cc77f6b78c4b8821c910518c259ab414 | |
| parent | 005cdf5953a165c5f117102382cd38ecb641fa2f (diff) | |
| parent | 12c3df39097905f981a392dceaa93ac879813159 (diff) | |
Merge pull request #120 from gkrishnaks/master
Typo correction in variable declaration, and closes feature requests #104, #86, #72 and fix for issue #115, updated excludeMatch() for faster regex check.
| -rw-r--r-- | css/redirector.css | 2 | ||||
| -rw-r--r-- | help.html | 24 | ||||
| -rw-r--r-- | js/background.js | 192 | ||||
| -rw-r--r-- | js/controllers/editredirect.js | 2 | ||||
| -rw-r--r-- | js/controllers/importexport.js | 4 | ||||
| -rw-r--r-- | js/controllers/redirectorpage.js | 52 | ||||
| -rw-r--r-- | js/popup.js | 17 | ||||
| -rw-r--r-- | js/redirect.js | 7 | ||||
| -rw-r--r-- | manifest.json | 2 | ||||
| -rw-r--r-- | popup.html | 4 | ||||
| -rw-r--r-- | redirector.html | 14 | 
11 files changed, 294 insertions, 26 deletions
diff --git a/css/redirector.css b/css/redirector.css index 26f9c1a..e2df85f 100644 --- a/css/redirector.css +++ b/css/redirector.css @@ -269,7 +269,7 @@ a.disabled:hover {    margin-left:-260px;    top:50%;    margin-top:-220px; -  height:220px; +  height:255px;  }  #delete-redirect-form div{ @@ -33,6 +33,7 @@  			</li>  			<li><a href="#wildcards">Wildcards</a></li>  			<li><a href="#regularexpressions">Regular expressions</a></li> +			<li><a href="#storageArea">Storage Area (Sync vs Local)</a></li>  			<li><a href="#examples">Examples</a>  				<ol>  					<li><a href="#ex1">Static redirect</a></li> @@ -149,7 +150,28 @@  		the regular expressions. Captures are specified with parentheses. Example: <span class="pattern">http://example.com/index.asp\?id=(\d+)</span> will match the url  		<span class="url">http://example.com/index.asp?id=12345</span> and $1 will be replaced by 12345. (A common mistake in regex patterns is to forget to escape  		the ? sign in the querystring of the url. ? is a special character in regular expressions so if you want to match an url with a querystring -		you should escape it as \?).</p> +		you should escape it as \?). To test your regular expressions, you may use any website or service. For example, <a href="https://regexr.com" target="_blank">regexr.com</a> </p> +		 +		<a name="storageArea"></a> +		<h4>Storage Area (Sync vs Local)</h4> + +		<p>Storage Area, by default, is set to Local. If you wish to sync your redirector rules across devices, you may choose to enable Sync from Settings page. +		When you toggle to Sync, data will be copied over to Sync storage and local storage will be deleted.  +		Similary, sync storage will be deleted if you disable sync and data will be moved to Local storage. +		<div>	 +		<em>Note:</em><ol> <li>Google Chrome Sync and Mozilla Firefox Sync limits the storage size as per below.  +		This limit is decided by browser vendors and Redirector addon cannot do anything about changing the below.</li> +		<li>You need to use chrome/firefox settings to setup a sync account for syncing to work.  +			If that is not completed, Sync will just act like local storage - take note of the storage sizes below. +			If sync account is not setup in chrome/firefox browser settings, leave the storage area to LOCAL as it has much larger size than Sync storage size. +		</li> </ol>		 +		 +		<ul> +			<li>Local Storage: 5 MB  - Redirector uses this as Default upon its installation</li> +			<li>Sync Storage : 0.008192 MB to store "Redirects" (8192 bytes) </li> +		</ul> +</div> +		</p>  		<a name="examples"></a>  		<h4>Examples</h4> diff --git a/js/background.js b/js/background.js index d1a939e..e087c22 100644 --- a/js/background.js +++ b/js/background.js @@ -7,7 +7,9 @@ function log(msg) {  	}  }  log.enabled = false; +var enableNotifications=false; +var storageArea = chrome.storage.local;  //Redirects partitioned by request type, so we have to run through  //the minimum number of redirects for each request.  var partitionedRedirects = {}; @@ -66,7 +68,7 @@ function checkRedirects(details) {  		return {};  	} - +	  	for (var i = 0; i < list.length; i++) {  		var r = list[i];  		var result = r.getMatch(details.url); @@ -90,8 +92,10 @@ function checkRedirects(details) {  			} -			log('Redirecting ' + details.url + ' ===> ' + result.redirectTo + ', type: ' + details.type + ', pattern: ' + r.includePattern); - +			log('Redirecting ' + details.url + ' ===> ' + result.redirectTo + ', type: ' + details.type + ', pattern: ' + r.includePattern + ' which is in Rule : ' + r.description); +			if(enableNotifications){ +				sendNotifications(r, details.url, result.redirectTo); +			}  			ignoreNextRequest[result.redirectTo] = new Date().getTime();  			return { redirectUrl: result.redirectTo }; @@ -125,7 +129,11 @@ function monitorChanges(changes, namespace) {      if (changes.logging) {          log('Logging settings have changed, updating...');          updateLogging(); -    } +	} +	if (changes.enableNotifications){ +		log('notifications setting changed'); +		enableNotifications=changes.enableNotifications.newValue; +	}  }  chrome.storage.onChanged.addListener(monitorChanges); @@ -135,9 +143,14 @@ function createFilter(redirects) {  	var types = [];  	for (var i = 0; i < redirects.length; i++) {  		redirects[i].appliesTo.forEach(function(type) {  +			// Added this condition below as part of fix for issue 115 https://github.com/einaregilsson/Redirector/issues/115 +			// Firefox considers responsive web images request as imageset. Chrome doesn't. +			// Chrome throws an error for imageset type, so let's add to 'types' only for the values that chrome or firefox supports +			if(chrome.webRequest.ResourceType[type.toUpperCase()]!== undefined){  			if (types.indexOf(type) == -1) {  				types.push(type);  			} +		}  		});  	}  	types.sort(); @@ -171,7 +184,7 @@ function setUpRedirectListener() {  	chrome.webRequest.onBeforeRequest.removeListener(checkRedirects); //Unsubscribe first, in case there are changes... -	chrome.storage.local.get({redirects:[]}, function(obj) { +	storageArea.get({redirects:[]}, function(obj) {  		var redirects = obj.redirects;  		if (redirects.length == 0) {  			log('No redirects defined, not setting up listener'); @@ -201,7 +214,9 @@ chrome.runtime.onMessage.addListener(  		log('Received background message: ' + JSON.stringify(request));  		if (request.type == 'getredirects') {  			log('Getting redirects from storage'); -			chrome.storage.local.get({redirects:[]}, function(obj) { +			storageArea.get({ +				redirects: [] +			}, function (obj) {  				log('Got redirects from storage: ' + JSON.stringify(obj));  				sendResponse(obj);  				log('Sent redirects to content page'); @@ -209,10 +224,95 @@ chrome.runtime.onMessage.addListener(  		} else if (request.type == 'saveredirects') {  			console.log('Saving redirects, count=' + request.redirects.length);  			delete request.type; -			chrome.storage.local.set(request, function(a) { +			storageArea.set(request, function (a) { +				if(chrome.runtime.lastError) { +				 if(chrome.runtime.lastError.message.indexOf("QUOTA_BYTES_PER_ITEM quota exceeded")>-1){ +					log("Redirects failed to save as size of redirects larger than allowed limit per item by Sync"); +					sendResponse({ +						message: "Redirects failed to save as size of redirects larger than what's allowed by Sync. Refer Help Page" +					}); +				 } +				} else {  				log('Finished saving redirects to storage'); -				sendResponse({message:"Redirects saved"}); +				sendResponse({ +					message: "Redirects saved" +				}); +			}  			}); +		} else if (request.type == 'ToggleSync') { +			// Notes on Toggle Sync feature here https://github.com/einaregilsson/Redirector/issues/86#issuecomment-389943854 +			// This provides for feature request - issue 86 +			delete request.type; +			log('toggling sync to ' + request.isSyncEnabled); +			// Setting for Sync enabled or not, resides in Local. +			chrome.storage.local.set({ +					isSyncEnabled: request.isSyncEnabled +				}, +				function () { +					if (request.isSyncEnabled) { +						storageArea = chrome.storage.sync; +						log('storageArea size for sync is 5 MB but one object (redirects) is allowed to hold only ' + storageArea.QUOTA_BYTES_PER_ITEM  / 1000000 + ' MB, that is .. ' + storageArea.QUOTA_BYTES_PER_ITEM  + " bytes"); +						chrome.storage.local.getBytesInUse("redirects", +							function (size) { +								log("size of redirects is " + size + " bytes"); +								if (size > storageArea.QUOTA_BYTES_PER_ITEM) { +									log("size of redirects " + size + " is greater than allowed for Sync which is " + storageArea.QUOTA_BYTES_PER_ITEM); +									// Setting storageArea back to Local. +									storageArea = chrome.storage.local;  +									sendResponse({ +										message: "Sync Not Possible - size of Redirects larger than what's allowed by Sync. Refer Help page" +									}); +								} else { +									chrome.storage.local.get({ +										redirects: [] +									}, function (obj) { +										//check if at least one rule is there. +										if (obj.redirects.length>0) { +											chrome.storage.sync.set(obj, function (a) { +												log('redirects moved from Local to Sync Storage Area'); +												//Remove Redirects from Local storage +												chrome.storage.local.remove("redirects"); +												// Call setupRedirectListener to setup the redirects  +												setUpRedirectListener(); +												sendResponse({ +													message: "syncEnabled" +												}); +											}); +										} else { +											log('No redirects are setup currently in Local, just enabling Sync'); +											sendResponse({ +												message: "syncEnabled" +											}); +										} +									}); +								} +							}); +						} else { +						storageArea = chrome.storage.local; +						log('storageArea size for local is ' + storageArea.QUOTA_BYTES / 1000000 + ' MB, that is .. ' + storageArea.QUOTA_BYTES + " bytes"); +						chrome.storage.sync.get({ +							redirects: [] +						}, function (obj) { +							if (obj.redirects.length>0) { +								chrome.storage.local.set(obj, function (a) { +									log('redirects moved from Sync to Local Storage Area'); +									//Remove Redirects from sync storage +									chrome.storage.sync.remove("redirects"); +									// Call setupRedirectListener to setup the redirects  +									setUpRedirectListener(); +									sendResponse({ +										message: "syncDisabled" +									}); +								}); +							} else { +								sendResponse({ +									message: "syncDisabled" +								}); +							} +						}); +					} +				}); +  		} else {  			log('Unexpected message: ' + JSON.stringify(request));  			return false; @@ -234,12 +334,78 @@ function updateLogging() {  }  updateLogging(); -chrome.storage.local.get({disabled:false}, function(obj) { -	if (!obj.disabled) { -		setUpRedirectListener(); +chrome.storage.local.get({ +	isSyncEnabled: false +}, function (obj) { +	if (obj.isSyncEnabled) { +		storageArea = chrome.storage.sync;  	} else { -		log('Redirector is disabled'); +		storageArea = chrome.storage.local;  	} +	// Now we know which storageArea to use, call setupInitial function +	setupInitial();   }); + +//wrapped the below inside a function so that we can call this once we know the value of storageArea from above.  + +function setupInitial() { +	chrome.storage.local.get({enableNotifications:false},function(obj){ +		enableNotifications = obj.enableNotifications; +	}); + +	chrome.storage.local.get({ +		disabled: false +	}, function (obj) { +		if (!obj.disabled) { +			setUpRedirectListener(); +		} else { +			log('Redirector is disabled'); +		} +	}); +}  log('Redirector starting up...'); -       
\ No newline at end of file +	 +// Below is a feature request by an user who wished to see visual indication for an Redirect rule being applied on URL  +// https://github.com/einaregilsson/Redirector/issues/72 +// By default, we will have it as false. If user wishes to enable it from settings page, we can make it true until user disables it (or browser is restarted) + +// Upon browser startup, just set enableNotifications to false. +// Listen to a message from Settings page to change this to true. +function sendNotifications(redirect, originalUrl, redirectedUrl ){ +	//var message = "Applied rule : " + redirect.description + " and redirected original page " + originalUrl + " to " + redirectedUrl; +	log("Showing redirect success notification"); +	//Firefox and other browsers does not yet support "list" type notification like in Chrome. +	// Console.log(JSON.stringify(chrome.notifications)); -- This will still show "list" as one option but it just won't work as it's not implemented by Firefox yet +	// Can't check if "chrome" typeof either, as Firefox supports both chrome and browser namespace. +	// So let's use useragent.  +	// Opera UA has both chrome and OPR. So check against that ( Only chrome which supports list) - other browsers to get BASIC type notifications. + +	if(navigator.userAgent.toLowerCase().indexOf("chrome") > -1 && navigator.userAgent.toLowerCase().indexOf("OPR")<0){ +		var items = [{title:"Original page: ", message: originalUrl},{title:"Redirected to: ",message:redirectedUrl}]; +		var head = "Redirector - Applied rule : " + redirect.description; +		chrome.notifications.create({ +			"type": "list", +			"items": items, +			"title": head, +			"message": head, +			"iconUrl": "images/icon-active-38.png" +		  });	} +	else{ +		var message = "Applied rule : " + redirect.description + " and redirected original page " + originalUrl + " to " + redirectedUrl; + +		chrome.notifications.create({ +        	"type": "basic", +        	"title": "Redirector", +			"message": message, +			"iconUrl": "images/icon-active-38.png" +		}); +	} +} + +chrome.runtime.onStartup.addListener(handleStartup); +function handleStartup(){ +	enableNotifications=false; +	chrome.storage.local.set({ +		enableNotifications: false +	}); +}
\ No newline at end of file diff --git a/js/controllers/editredirect.js b/js/controllers/editredirect.js index 3d63340..81a4ee9 100644 --- a/js/controllers/editredirect.js +++ b/js/controllers/editredirect.js @@ -79,7 +79,7 @@ redirectorApp.controller('EditRedirectCtrl', ['$scope', function($s) {  			arr.splice(index, 1);  		} -		var order = 'main_frame,sub_frame,stylesheet,script,image,object,xmlhttprequest,other'; +		var order = 'main_frame,sub_frame,stylesheet,script,image,imageset,object,xmlhttprequest,other';  		arr.sort(function(a,b) {  			return order.indexOf(a) - order.indexOf(b); diff --git a/js/controllers/importexport.js b/js/controllers/importexport.js index b9a90c7..5ea9756 100644 --- a/js/controllers/importexport.js +++ b/js/controllers/importexport.js @@ -51,7 +51,7 @@ redirectorApp.config([   		reader.onload = function(e) {   			var data;   			try { -	 			var data = JSON.parse(reader.result); +	 			data = JSON.parse(reader.result);   			} catch(e) {   				$s.showMessage('Failed to parse JSON data, invalid JSON: ' + (e.message||'').substr(0,100));  	 			return $s.$parent.$apply(); @@ -107,4 +107,4 @@ redirectorApp.config([   	}   	$s.updateExportLink(); //Run once so the a will have a href to begin with -}]);
\ No newline at end of file +}]); diff --git a/js/controllers/redirectorpage.js b/js/controllers/redirectorpage.js index d18883c..e9ed30c 100644 --- a/js/controllers/redirectorpage.js +++ b/js/controllers/redirectorpage.js @@ -19,10 +19,45 @@ redirectorApp.controller('RedirectorPageCtrl', ['$scope', '$timeout', function($  		var arr = $s.redirects.map(normalize);  		chrome.runtime.sendMessage({type:"saveredirects", redirects:arr}, function(response) { -			console.log('Saved ' + arr.length + ' redirects at ' + new Date() + '. Message from background page:' + response.message); +			console.log(response.message); +			if(response.message.indexOf("Redirects failed to save") > -1){ +				$s.showMessage(response.message, false); +			}else{ +				console.log('Saved ' + arr.length + ' redirects at ' + new Date() + '. Message from background page:' + response.message); +		    }  		});  	} -  +	 +	// Default is LOCAL storage, allow user to select toggle to Sync if they wish  +	$s.isSyncEnabled = false; +	 +	chrome.storage.local.get({isSyncEnabled:false},function(obj){ +		$s.isSyncEnabled = obj.isSyncEnabled; +		$s.$apply();		 +	}); +	 +	$s.toggleSyncSetting = function(){ +		chrome.runtime.sendMessage({type:"ToggleSync", isSyncEnabled: !$s.isSyncEnabled}, function(response) { +			if(response.message === "syncEnabled"){ +				$s.isSyncEnabled = true; +				$s.showMessage('Sync is enabled!',true); +			} else if(response.message === "syncDisabled"){ +				$s.isSyncEnabled = false; +				$s.showMessage('Sync is disabled - local storage will be used!',true); +			} else if(response.message.indexOf("Sync Not Possible")>-1){ +				$s.isSyncEnabled = false; +				chrome.storage.local.set({isSyncEnabled: $s.isSyncEnabled}, function(){ +				 // console.log("set back to false"); +				}); +				$s.showMessage(response.message, false); +			} +			else { +				$s.showMessage('Error occured when trying to change Sync settings. Refer logging and raise an issue',false); +			} +			$s.$apply(); +		}); +	} +	   	$s.redirects = [];  	//Need to proxy this through the background page, because Firefox gives us dead objects @@ -39,12 +74,21 @@ redirectorApp.controller('RedirectorPageCtrl', ['$scope', '$timeout', function($   	$s.showMessage = function(message, success) {   		$s.message = message;   		$s.messageType = success ? 'success' : 'error'; +		var timer = 20; +		/*if($s.message.indexOf("Error occured")>-1 || $s.message.indexOf("Sync Not Possible")>-1 || $s.message.indexOf("Redirects failed to save")>-1 ){ +			timer = 10;  +			//  just to reload the page - when I tested, $s.$apply() didn't refresh as I expected for "Sync Not Possible". +			// Reloading the page is going to getRedirects and show actual values to user after showing 10 seconds error message +		} */   		//Remove the message in 20 seconds if it hasn't been changed...   		$timeout(function() {   			if ($s.message == message) {   				$s.message = null; - 			} - 		}, 20 * 1000); +			 } +			/* if(timer == 10){ +				chrome.tabs.reload(); +			 } */ + 		}, timer * 1000);   	}  }]); diff --git a/js/popup.js b/js/popup.js index 6658fc0..c27a29f 100644 --- a/js/popup.js +++ b/js/popup.js @@ -30,6 +30,23 @@ angular.module('popupApp', []).controller('PopupCtrl', ['$scope', function($s) {  		});  	}; +	 +	//Toggle Notifications by sending a notifications +	$s.enableNotifications = false;  +	 +	storage.get({enableNotifications:false},function(obj){ +		$s.enableNotifications = obj.enableNotifications; +		$s.$apply(); +	}); +	 +	$s.toggleNotifications=function(){ +		storage.get({enableNotifications:false},function(obj){ +		storage.set({enableNotifications:!obj.enableNotifications}); +			$s.enableNotifications = !obj.enableNotifications; +			$s.$apply(); +	}); +	} +  	$s.openRedirectorSettings = function() {  		//switch to open one if we have it to minimize conflicts diff --git a/js/redirect.js b/js/redirect.js index 83681fc..ac06ff7 100644 --- a/js/redirect.js +++ b/js/redirect.js @@ -18,6 +18,7 @@ Redirect.requestTypes = {  	stylesheet : "Stylesheets",  	script : "Scripts",  	image : "Images", +	imageset: "Responsive Images in Firefox",  	object : "Objects (e.g. Flash videos, Java applets)",  	xmlhttprequest : "XMLHttpRequests (Ajax)",  	other : "Other" @@ -33,6 +34,7 @@ Redirect.prototype = {  	error : null,  	includePattern : '',  	excludePattern : '', +	patternDesc:'',  	redirectUrl : '',  	patternType : '',  	processMatches : 'noProcessing', @@ -56,6 +58,7 @@ Redirect.prototype = {  			&& this.exampleUrl == redirect.exampleUrl  			&& this.includePattern == redirect.includePattern  			&& this.excludePattern == redirect.excludePattern +			&& this.patternDesc == redirect.patternDesc  			&& this.redirectUrl == redirect.redirectUrl  			&& this.patternType == redirect.patternType  			&& this.processMatches == redirect.processMatches @@ -70,6 +73,7 @@ Redirect.prototype = {  			error : this.error,  			includePattern : this.includePattern,  			excludePattern : this.excludePattern, +			patternDesc : this.patternDesc,  			redirectUrl : this.redirectUrl,  			patternType : this.patternType,  			processMatches : this.processMatches, @@ -208,6 +212,7 @@ Redirect.prototype = {  		this.excludePattern = o.excludePattern || '';  		this.redirectUrl = o.redirectUrl || '';  		this.patternType = o.patternType || Redirect.WILDCARD; +		this.patternDesc = o.patternDesc || '';  		this.processMatches = o.processMatches || 'noProcessing';  		if (!o.processMatches && o.unescapeMatches) {  			this.processMatches = 'urlDecode'; @@ -261,7 +266,7 @@ Redirect.prototype = {  		if (!this._rxExclude) {  			return false;	  		} -		var shouldExclude = !!this._rxExclude.exec(url);	 +		var shouldExclude = this._rxExclude.test(url);	  		this._rxExclude.lastIndex = 0;  		return shouldExclude;  	} diff --git a/manifest.json b/manifest.json index e3128f1..1325ba9 100644 --- a/manifest.json +++ b/manifest.json @@ -11,7 +11,7 @@                 "64": "images/icon-active-64.png",                "128": "images/icon-active-128.png" }, -  "permissions" : ["webRequest", "webRequestBlocking", "storage", "tabs", "http://*/*", "https://*/*"], +  "permissions" : ["webRequest", "webRequestBlocking", "storage", "tabs", "http://*/*", "https://*/*", "notifications"],    "applications": {      "gecko": { @@ -12,6 +12,8 @@  		<div class="disabled"><span ng-show="disabled">Disabled</span></div>  		<button ng-click="toggleDisabled()">{{disabled ? 'Enable Redirector' : 'Disable Redirector'}}</button>          <button ng-click="openRedirectorSettings()">Edit Redirects</button> -        <label><input type="checkbox" ng-model="logging" ng-click="toggleLogging()" /> Logging enabled</label> +		<label><input type="checkbox" ng-model="logging" ng-click="toggleLogging()" /> Logging enabled</label> +		<br/> +		<label><input type="checkbox" ng-model="enableNotifications" ng-click="toggleNotifications()" /> Enable Notifications</label>  	</body>  </html> diff --git a/redirector.html b/redirector.html index 1785f0e..eb27611 100644 --- a/redirector.html +++ b/redirector.html @@ -46,7 +46,11 @@  			<div>  				<label>Pattern type:</label>  				<span>{{redirect.patternType == 'W' ? 'Wildcard' : 'Regular Expression'}}</span> -			</div>			 +			</div> +			<div> +				<label>Pattern Description:</label> +				<span>{{redirect.patternDesc}}</span> +			</div>				  			<div class="button-container">  				<a class="btn red large" ng-click="deleteRedirect()">Yes, delete it</a>  				<a class="btn grey large" ng-click="cancelDelete()">No, don't delete it</a> @@ -84,6 +88,10 @@  					</div>  				</div>  				<div> +					<label>Pattern Description:</label> +					<div class="input-cell"><input type="text" ng-model="redirect.patternDesc" placeholder="Describe your pattern" /></div> +				</div> +				<div>  					<label>Example result:</label>  					<div class="input-cell"><span class="error example-result-error" ng-show="redirect.error">{{redirect.error}}</span><span class="example-result" ng-show="redirect.exampleResult">{{redirect.exampleResult}}</span></div>  				</div> @@ -166,6 +174,9 @@  						<div ng-show="r.excludePattern">  							<label>excluding:</label><p>{{r.excludePattern}}</p>  						</div> +						<div ng-if="r.patternDesc"> +							<label>Hint:</label><p>{{r.patternDesc}}</p> +						</div>  						<div>  							<label>Example:</label> <p><span class="error" ng-show="r.error">{{r.error}}</span><span ng-show="r.exampleResult">{{r.exampleUrl}} <span class="arrow">→</span> {{r.exampleResult}}</span></p>  						</div> @@ -182,6 +193,7 @@  						<a class="btn medium grey padded" ng-click="duplicateRedirect($index)">Duplicate</a>  					</div>  				</div> +				<label><input type="checkbox" ng-model="isSyncEnabled" ng-click="toggleSyncSetting()" /> Enable Storage Sync</label>  			</div>  			<footer>  | 
