chromium/third_party/blink/web_tests/external/wpt/common/PrefixedLocalStorage.js

/**
 * Supports pseudo-"namespacing" localStorage for a given test
 * by generating and using a unique prefix for keys. Why trounce on other
 * tests' localStorage items when you can keep it "separated"?
 *
 * PrefixedLocalStorageTest: Instantiate in testharness.js tests to generate
 *   a new unique-ish prefix
 * PrefixedLocalStorageResource: Instantiate in supporting test resource
 *   files to use/share a prefix generated by a test.
 */
var PrefixedLocalStorage = function () {
  this.prefix = ''; // Prefix for localStorage keys
  this.param = 'prefixedLocalStorage'; // Param to use in querystrings
};

PrefixedLocalStorage.prototype.clear = function () {
  if (this.prefix === '') { return; }
  Object.keys(localStorage).forEach(sKey => {
    if (sKey.indexOf(this.prefix) === 0) {
      localStorage.removeItem(sKey);
    }
  });
};

/**
 * Append/replace prefix parameter and value in URI querystring
 * Use to generate URLs to resource files that will share the prefix.
 */
PrefixedLocalStorage.prototype.url = function (uri) {
  function updateUrlParameter (uri, key, value) {
    var i         = uri.indexOf('#');
    var hash      = (i === -1) ? '' : uri.substr(i);
    uri           = (i === -1) ? uri : uri.substr(0, i);
    var re        = new RegExp(`([?&])${key}=.*?(&|$)`, 'i');
    var separator = uri.indexOf('?') !== -1 ? '&' : '?';
    uri = (uri.match(re)) ? uri.replace(re, `$1${key}=${value}$2`) :
      `${uri}${separator}${key}=${value}`;
    return uri + hash;
  }
  return updateUrlParameter(uri, this.param, this.prefix);
};

PrefixedLocalStorage.prototype.prefixedKey = function (baseKey) {
  return `${this.prefix}${baseKey}`;
};

PrefixedLocalStorage.prototype.setItem = function (baseKey, value) {
  localStorage.setItem(this.prefixedKey(baseKey), value);
};

/**
 * Listen for `storage` events pertaining to a particular key,
 * prefixed with this object's prefix. Ignore when value is being set to null
 * (i.e. removeItem).
 */
PrefixedLocalStorage.prototype.onSet = function (baseKey, fn) {
  window.addEventListener('storage', e => {
    var match = this.prefixedKey(baseKey);
    if (e.newValue !== null && e.key.indexOf(match) === 0) {
      fn.call(this, e);
    }
  });
};

/*****************************************************************************
 * Use in a testharnessjs test to generate a new key prefix.
 * async_test(t => {
 *   var prefixedStorage = new PrefixedLocalStorageTest();
 *   t.add_cleanup(() => prefixedStorage.cleanup());
 *   /...
 * });
 */
var PrefixedLocalStorageTest = function () {
  PrefixedLocalStorage.call(this);
  this.prefix = `${document.location.pathname}-${Math.random()}-${Date.now()}-`;
};
PrefixedLocalStorageTest.prototype = Object.create(PrefixedLocalStorage.prototype);
PrefixedLocalStorageTest.prototype.constructor = PrefixedLocalStorageTest;

/**
 * Use in a cleanup function to clear out prefixed entries in localStorage
 */
PrefixedLocalStorageTest.prototype.cleanup = function () {
  this.setItem('closeAll', 'true');
  this.clear();
};

/*****************************************************************************
 * Use in test resource files to share a prefix generated by a
 * PrefixedLocalStorageTest. Will look in URL querystring for prefix.
 * Setting `close_on_cleanup` opt truthy will make this script's window listen
 * for storage `closeAll` event from controlling test and close itself.
 *
 * var PrefixedLocalStorageResource({ close_on_cleanup: true });
 */
var PrefixedLocalStorageResource = function (options) {
  PrefixedLocalStorage.call(this);
  this.options = Object.assign({}, {
    close_on_cleanup: false
  }, options || {});
  // Check URL querystring for prefix to use
  var regex = new RegExp(`[?&]${this.param}(=([^&#]*)|&|#|$)`),
    results = regex.exec(document.location.href);
  if (results && results[2]) {
    this.prefix = results[2];
  }
  // Optionally have this window close itself when the PrefixedLocalStorageTest
  // sets a `closeAll` item.
  if (this.options.close_on_cleanup) {
    this.onSet('closeAll', () => {
      window.close();
    });
  }
};
PrefixedLocalStorageResource.prototype = Object.create(PrefixedLocalStorage.prototype);
PrefixedLocalStorageResource.prototype.constructor = PrefixedLocalStorageResource;