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