1/** 2 * Supports pseudo-"namespacing" localStorage for a given test 3 * by generating and using a unique prefix for keys. Why trounce on other 4 * tests' localStorage items when you can keep it "separated"? 5 * 6 * PrefixedLocalStorageTest: Instantiate in testharness.js tests to generate 7 * a new unique-ish prefix 8 * PrefixedLocalStorageResource: Instantiate in supporting test resource 9 * files to use/share a prefix generated by a test. 10 */ 11var PrefixedLocalStorage = function () { 12 this.prefix = ''; // Prefix for localStorage keys 13 this.param = 'prefixedLocalStorage'; // Param to use in querystrings 14}; 15 16PrefixedLocalStorage.prototype.clear = function () { 17 if (this.prefix === '') { return; } 18 Object.keys(localStorage).forEach(sKey => { 19 if (sKey.indexOf(this.prefix) === 0) { 20 localStorage.removeItem(sKey); 21 } 22 }); 23}; 24 25/** 26 * Append/replace prefix parameter and value in URI querystring 27 * Use to generate URLs to resource files that will share the prefix. 28 */ 29PrefixedLocalStorage.prototype.url = function (uri) { 30 function updateUrlParameter (uri, key, value) { 31 var i = uri.indexOf('#'); 32 var hash = (i === -1) ? '' : uri.substr(i); 33 uri = (i === -1) ? uri : uri.substr(0, i); 34 var re = new RegExp(`([?&])${key}=.*?(&|$)`, 'i'); 35 var separator = uri.indexOf('?') !== -1 ? '&' : '?'; 36 uri = (uri.match(re)) ? uri.replace(re, `$1${key}=${value}$2`) : 37 `${uri}${separator}${key}=${value}`; 38 return uri + hash; 39 } 40 return updateUrlParameter(uri, this.param, this.prefix); 41}; 42 43PrefixedLocalStorage.prototype.prefixedKey = function (baseKey) { 44 return `${this.prefix}${baseKey}`; 45}; 46 47PrefixedLocalStorage.prototype.setItem = function (baseKey, value) { 48 localStorage.setItem(this.prefixedKey(baseKey), value); 49}; 50 51/** 52 * Listen for `storage` events pertaining to a particular key, 53 * prefixed with this object's prefix. Ignore when value is being set to null 54 * (i.e. removeItem). 55 */ 56PrefixedLocalStorage.prototype.onSet = function (baseKey, fn) { 57 window.addEventListener('storage', e => { 58 var match = this.prefixedKey(baseKey); 59 if (e.newValue !== null && e.key.indexOf(match) === 0) { 60 fn.call(this, e); 61 } 62 }); 63}; 64 65/***************************************************************************** 66 * Use in a testharnessjs test to generate a new key prefix. 67 * async_test(t => { 68 * var prefixedStorage = new PrefixedLocalStorageTest(); 69 * t.add_cleanup(() => prefixedStorage.cleanup()); 70 * /... 71 * }); 72 */ 73var PrefixedLocalStorageTest = function () { 74 PrefixedLocalStorage.call(this); 75 this.prefix = `${document.location.pathname}-${Math.random()}-${Date.now()}-`; 76}; 77PrefixedLocalStorageTest.prototype = Object.create(PrefixedLocalStorage.prototype); 78PrefixedLocalStorageTest.prototype.constructor = PrefixedLocalStorageTest; 79 80/** 81 * Use in a cleanup function to clear out prefixed entries in localStorage 82 */ 83PrefixedLocalStorageTest.prototype.cleanup = function () { 84 this.setItem('closeAll', 'true'); 85 this.clear(); 86}; 87 88/***************************************************************************** 89 * Use in test resource files to share a prefix generated by a 90 * PrefixedLocalStorageTest. Will look in URL querystring for prefix. 91 * Setting `close_on_cleanup` opt truthy will make this script's window listen 92 * for storage `closeAll` event from controlling test and close itself. 93 * 94 * var PrefixedLocalStorageResource({ close_on_cleanup: true }); 95 */ 96var PrefixedLocalStorageResource = function (options) { 97 PrefixedLocalStorage.call(this); 98 this.options = Object.assign({}, { 99 close_on_cleanup: false 100 }, options || {}); 101 // Check URL querystring for prefix to use 102 var regex = new RegExp(`[?&]${this.param}(=([^&#]*)|&|#|$)`), 103 results = regex.exec(document.location.href); 104 if (results && results[2]) { 105 this.prefix = results[2]; 106 } 107 // Optionally have this window close itself when the PrefixedLocalStorageTest 108 // sets a `closeAll` item. 109 if (this.options.close_on_cleanup) { 110 this.onSet('closeAll', () => { 111 window.close(); 112 }); 113 } 114}; 115PrefixedLocalStorageResource.prototype = Object.create(PrefixedLocalStorage.prototype); 116PrefixedLocalStorageResource.prototype.constructor = PrefixedLocalStorageResource; 117