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