1<!DOCTYPE html>
2<!--
3Copyright (c) 2014 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7<link rel="import" href="/tracing/base/base.html">
8<script>
9'use strict';
10
11/**
12 * @fileoverview Provides the Settings object.
13 */
14tr.exportTo('tr.b', function() {
15  /**
16   * Settings is a simple wrapper around local storage, to make it easier
17   * to test classes that have settings.
18   *
19   * May be called as new tr.b.Settings() or simply tr.b.Settings()
20   * @constructor
21   */
22  function Settings() {
23    return Settings;
24  };
25
26  if (tr.b.unittest && tr.b.unittest.TestRunner) {
27    tr.b.unittest.TestRunner.addEventListener(
28        'tr-unittest-will-run',
29        function() {
30          if (tr.isHeadless)
31            Settings.setAlternativeStorageInstance(new HeadlessStorage());
32          else
33            Settings.setAlternativeStorageInstance(global.sessionStorage);
34        });
35  }
36
37  function SessionSettings() {
38    return SessionSettings;
39  }
40
41  function AddStaticStorageFunctionsToClass_(input_class, storage) {
42    input_class.storage_ = storage;
43
44    /**
45     * Get the setting with the given name.
46     *
47     * @param {string} key The name of the setting.
48     * @param {string=} opt_default The default value to return if not set.
49     * @param {string=} opt_namespace If set, the setting name will be prefixed
50     * with this namespace, e.g. "categories.settingName". This is useful for
51     * a set of related settings.
52     */
53    input_class.get = function(key, opt_default, opt_namespace) {
54      key = input_class.namespace_(key, opt_namespace);
55      var rawVal = input_class.storage_.getItem(key);
56      if (rawVal === null || rawVal === undefined)
57        return opt_default;
58
59      // Old settings versions used to stringify objects instead of putting them
60      // into JSON. If those are encountered, parse will fail. In that case,
61      // "upgrade" the setting to the default value.
62      try {
63        return JSON.parse(rawVal).value;
64      } catch (e) {
65        input_class.storage_.removeItem(key);
66        return opt_default;
67      }
68    };
69
70    /**
71     * Set the setting with the given name to the given value.
72     *
73     * @param {string} key The name of the setting.
74     * @param {string} value The value of the setting.
75     * @param {string=} opt_namespace If set, the setting name will be prefixed
76     * with this namespace, e.g. "categories.settingName". This is useful for
77     * a set of related settings.
78     */
79    input_class.set = function(key, value, opt_namespace) {
80      if (value === undefined)
81        throw new Error('Settings.set: value must not be undefined');
82      var v = JSON.stringify({value: value});
83      input_class.storage_.setItem(
84          input_class.namespace_(key, opt_namespace), v);
85    };
86
87    /**
88     * Return a list of all the keys, or all the keys in the given namespace
89     * if one is provided.
90     *
91     * @param {string=} opt_namespace If set, only return settings which
92     * begin with this prefix.
93     */
94    input_class.keys = function(opt_namespace) {
95      var result = [];
96      opt_namespace = opt_namespace || '';
97      for (var i = 0; i < input_class.storage_.length; i++) {
98        var key = input_class.storage_.key(i);
99        if (input_class.isnamespaced_(key, opt_namespace))
100          result.push(input_class.unnamespace_(key, opt_namespace));
101      }
102      return result;
103    };
104
105    input_class.isnamespaced_ = function(key, opt_namespace) {
106      return key.indexOf(input_class.normalize_(opt_namespace)) == 0;
107    };
108
109    input_class.namespace_ = function(key, opt_namespace) {
110      return input_class.normalize_(opt_namespace) + key;
111    };
112
113    input_class.unnamespace_ = function(key, opt_namespace) {
114      return key.replace(input_class.normalize_(opt_namespace), '');
115    };
116
117    /**
118     * All settings are prefixed with a global namespace to avoid collisions.
119     * input_class may also be namespaced with an additional prefix passed into
120     * the get, set, and keys methods in order to group related settings.
121     * This method makes sure the two namespaces are always set properly.
122     */
123    input_class.normalize_ = function(opt_namespace) {
124      return input_class.NAMESPACE + (opt_namespace ? opt_namespace + '.' : '');
125    };
126
127    input_class.setAlternativeStorageInstance = function(instance) {
128      input_class.storage_ = instance;
129    };
130
131    input_class.getAlternativeStorageInstance = function() {
132      if (!tr.isHeadless && input_class.storage_ === localStorage)
133        return undefined;
134      return input_class.storage_;
135    };
136
137    input_class.NAMESPACE = 'trace-viewer';
138  };
139
140  function HeadlessStorage() {
141    this.length = 0;
142    this.hasItem_ = {};
143    this.items_ = {};
144    this.itemsAsArray_ = undefined;
145  }
146  HeadlessStorage.prototype = {
147    key: function(index) {
148      return this.itemsAsArray[index];
149    },
150
151    get itemsAsArray() {
152      if (this.itemsAsArray_ !== undefined)
153        return this.itemsAsArray_;
154      var itemsAsArray = [];
155      for (var k in this.items_)
156        itemsAsArray.push(k);
157      this.itemsAsArray_ = itemsAsArray;
158      return this.itemsAsArray_;
159    },
160
161    getItem: function(key) {
162      if (!this.hasItem_[key])
163        return null;
164      return this.items_[key];
165    },
166
167    removeItem: function(key) {
168      if (!this.hasItem_[key])
169        return;
170      var value = this.items_[key];
171      delete this.hasItem_[key];
172      delete this.items_[key];
173      this.length--;
174      this.itemsAsArray_ = undefined;
175      return value;
176    },
177
178    setItem: function(key, value) {
179      if (this.hasItem_[key]) {
180        this.items_[key] = value;
181        return;
182      }
183      this.items_[key] = value;
184      this.hasItem_[key] = true;
185      this.length++;
186      this.itemsAsArray_ = undefined;
187      return value;
188    }
189  };
190
191  if (tr.isHeadless) {
192    AddStaticStorageFunctionsToClass_(Settings, new HeadlessStorage());
193    AddStaticStorageFunctionsToClass_(SessionSettings, new HeadlessStorage());
194  } else {
195    AddStaticStorageFunctionsToClass_(Settings, localStorage);
196    AddStaticStorageFunctionsToClass_(SessionSettings, sessionStorage);
197  }
198
199  return {
200    Settings: Settings,
201    SessionSettings: SessionSettings
202  };
203});
204</script>
205