OLD | NEW |
1 // Copyright 2015 Google Inc. All Rights Reserved. | 1 // Copyright 2015 Google Inc. All Rights Reserved. |
2 // | 2 // |
3 // Licensed under the Apache License, Version 2.0 (the "License"); | 3 // Licensed under the Apache License, Version 2.0 (the "License"); |
4 // you may not use this file except in compliance with the License. | 4 // you may not use this file except in compliance with the License. |
5 // You may obtain a copy of the License at | 5 // You may obtain a copy of the License at |
6 // | 6 // |
7 // http://www.apache.org/licenses/LICENSE-2.0 | 7 // http://www.apache.org/licenses/LICENSE-2.0 |
8 // | 8 // |
9 // Unless required by applicable law or agreed to in writing, software | 9 // Unless required by applicable law or agreed to in writing, software |
10 // distributed under the License is distributed on an "AS IS" BASIS, | 10 // distributed under the License is distributed on an "AS IS" BASIS, |
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 // See the License for the specific language governing permissions and | 12 // See the License for the specific language governing permissions and |
13 // limitations under the License. | 13 // limitations under the License. |
14 | 14 |
15 /** | 15 /** |
16 * Polyfill for Chrome Apps' storage API. | 16 * Polyfill for Chrome Apps' storage API. |
17 */ | 17 */ |
18 | 18 |
19 'use strict'; | 19 'use strict'; |
20 | 20 |
21 if (!chrome.storage) | 21 if (!chrome.storage) |
22 chrome.storage = {}; | 22 chrome.storage = {}; |
23 | 23 |
| 24 chrome.caterpillar.storage = {}; |
| 25 |
24 (function() { | 26 (function() { |
25 | 27 |
26 /** | 28 /** |
27 * Triggers a chrome.storage.onChanged event on self. | 29 * Triggers a chrome.storage.onChanged event on self. |
28 * | 30 * |
29 * This is used internally within the polyfill to manage change events; event | 31 * This is used internally within the polyfill to manage change events; event |
30 * information is stored in event.detail. | 32 * information is stored in event.detail. |
31 * | 33 * |
32 * @param {object} items Object mapping keys to StorageChanges containing the | 34 * @param {object} items Object mapping keys to StorageChanges containing the |
33 * new and old values of each item. | 35 * new and old values of each item. |
34 */ | 36 */ |
35 function triggerOnChanged(items) { | 37 function triggerOnChanged(items) { |
36 var event = new CustomEvent('chrome.storage.onChanged', {'detail': items}); | 38 var event = new CustomEvent('chrome.storage.onChanged', {'detail': items}); |
37 self.dispatchEvent(event); | 39 self.dispatchEvent(event); |
38 }; | 40 }; |
39 | 41 |
40 /** | 42 /** |
| 43 * Stores onChanged event listeners. |
| 44 */ |
| 45 var onChangedListeners = []; |
| 46 |
| 47 /** |
41 * Represents a change in stored data. | 48 * Represents a change in stored data. |
42 */ | 49 */ |
43 chrome.storage.StorageChange = class { | 50 chrome.storage.StorageChange = class { |
44 /** | 51 /** |
45 * @param {object=} opt_oldValue The old value of the item. | 52 * @param {object=} opt_oldValue The old value of the item. |
46 * @param {object=} opt_newValue The new value of the item. | 53 * @param {object=} opt_newValue The new value of the item. |
47 */ | 54 */ |
48 constructor(opt_oldValue, opt_newValue) { | 55 constructor(opt_oldValue, opt_newValue) { |
49 this.oldValue = opt_oldValue; | 56 this.oldValue = opt_oldValue; |
50 this.newValue = opt_newValue; | 57 this.newValue = opt_newValue; |
51 }; | 58 } |
52 }; | 59 }; |
53 | 60 |
54 /** | 61 /** |
55 * Represents an area where data can be stored. | 62 * Represents an area where data can be stored. |
56 * | 63 * |
57 * Chrome Apps have three such areas - sync, local, managed. | 64 * Chrome Apps have three such areas - sync, local, managed. |
58 */ | 65 */ |
59 chrome.storage.StorageArea = class { | 66 chrome.storage.StorageArea = class { |
60 /** | 67 /** |
61 * Gets one or more items from storage. | 68 * Gets one or more items from storage. |
62 * | 69 * |
63 * @param {string | string[] | object} opt_keys A single key to get, list | 70 * @param {string= | string[]= | object=} opt_keys A single key to get, list |
64 * of keys to get, or a dictionary specifying default values. Empty lists | 71 * of keys to get, or a dictionary specifying default values. Empty lists |
65 * or objects return empty results. Pass null to get all contents. | 72 * or objects return empty results. Pass null to get all contents. |
66 * @param {function} callback Callback with storage items, or on failure. | 73 * @param {function} callback Callback with storage items, or on failure. |
67 */ | 74 */ |
68 get(opt_keys, callback) { | 75 get(opt_keys, callback) { |
69 // Handle the optional argument being *first*. | 76 // Juggle arguments. |
70 if (callback === undefined) { | 77 if (callback === undefined) { |
71 // Keys wasn't actually given, the callback was. | 78 // Keys wasn't actually given, the callback was. |
72 callback = opt_keys; | 79 callback = opt_keys; |
73 opt_keys = null; | 80 opt_keys = null; |
74 } | 81 } |
75 | 82 |
76 // Four scenarios: | 83 // Four scenarios: |
77 // 1. Input is a single string key; retrieve associated value. | 84 // 1. Input is a single string key; retrieve associated value. |
78 // 2. Input is multiple string keys; retrieve associated values. | 85 // 2. Input is multiple string keys; retrieve associated values. |
79 // 3. Input is a map from string key to default value; retrieve associated | 86 // 3. Input is a map from string key to default value; retrieve associated |
80 // values and rely on defaults if necessary. | 87 // values and rely on defaults if necessary. |
81 // 4. Input is null; retrieve all key/value pairs. | 88 // 4. Input is null; retrieve all key/value pairs. |
82 | 89 |
83 var handleError = function(err) { | 90 var handleError = function(err) { |
84 chrome.caterpillar.setError('Error retrieving values: ' + err); | 91 chrome.caterpillar.setError('Error retrieving values: ' + err); |
85 callback(); | 92 callback(); |
86 } | 93 } |
87 | 94 |
| 95 if (typeof opt_keys === 'string') |
| 96 opt_keys = [opt_keys]; |
| 97 |
88 if (opt_keys === null) { | 98 if (opt_keys === null) { |
89 // null input; get all key/value pairs. | 99 // null input; get all key/value pairs. |
90 var items = {}; | 100 var items = {}; |
91 localforage.iterate(function (value, key) { items[key] = value; }) | 101 localforage.iterate(function (value, key) { items[key] = value; }) |
92 .then(callback.bind(this, items)) | 102 .then(callback.bind(this, items)) |
93 .catch(handleError); | 103 .catch(handleError); |
94 } else if (Array.isArray(opt_keys)) { | 104 } else if (Array.isArray(opt_keys)) { |
95 // Array input; get associated values of each key. | 105 // Array input; get associated values of each key. |
96 var valuePromises = opt_keys.map(key => localforage.getItem(key)); | 106 var valuePromises = opt_keys.map(key => localforage.getItem(key)); |
97 Promise.all(valuePromises) | 107 Promise.all(valuePromises) |
98 .then(values => { | 108 .then(values => { |
99 // The callback expects a map from keys to values, but | 109 // The callback expects a map from keys to values, but |
100 // localforage just gives us values. | 110 // localforage just gives us values. |
101 var items = {}; | 111 var items = {}; |
102 for (var i = 0; i < opt_keys.length; i++) { | 112 for (var i = 0; i < opt_keys.length; i++) { |
103 items[opt_keys[i]] = values[i]; | 113 items[opt_keys[i]] = values[i]; |
104 } | 114 } |
105 callback(items); | 115 callback(items); |
106 }) | 116 }) |
107 .catch(handleError); | 117 .catch(handleError); |
108 } else if (typeof opt_keys === 'string') { | |
109 // String input; get associated value of the key. | |
110 localforage.getItem(opt_keys).then(value => { | |
111 var items = {}; | |
112 items[opt_keys] = value; | |
113 callback(items); | |
114 }).catch(handleError); | |
115 } else { | 118 } else { |
116 // Object input; get associated values with defaults. | 119 // Object input; get associated values with defaults. |
117 var keys = Object.keys(opt_keys); | 120 var keys = Object.keys(opt_keys); |
118 Promise.all(keys.map(key => localforage.getItem(key))) | 121 Promise.all(keys.map(key => localforage.getItem(key))) |
119 .then(values => { | 122 .then(values => { |
120 var items = {}; | 123 var items = {}; |
121 for (var i = 0; i < keys.length; i++) { | 124 for (var i = 0; i < keys.length; i++) { |
122 if (values[i] === null) { | 125 if (values[i] === null) { |
123 items[keys[i]] = opt_keys[keys[i]]; | 126 items[keys[i]] = opt_keys[keys[i]]; |
124 } else { | 127 } else { |
125 items[keys[i]] = values[i]; | 128 items[keys[i]] = values[i]; |
126 } | 129 } |
127 } | 130 } |
128 callback(items); | 131 callback(items); |
129 }) | 132 }) |
130 .catch(handleError); | 133 .catch(handleError); |
131 } | 134 } |
132 } | 135 } |
| 136 |
| 137 /** |
| 138 * Gets the amount of space (in bytes) being used by one or more items. |
| 139 * |
| 140 * Not implemented in this polyfill. |
| 141 * |
| 142 * @param {string= | string[]= | object=} opt_keys A single key to get, |
| 143 * list of keys to get, or a dictionary specifying default values. Empty |
| 144 * lists or objects return 0. Pass null to get total usage. |
| 145 * @param {function} callback Callback with bytes in use, or failure. |
| 146 */ |
| 147 getBytesInUse(opt_keys, callback) { |
| 148 // Juggle arguments. |
| 149 if (callback === undefined) { |
| 150 callback = opt_keys; |
| 151 opt_keys = null; |
| 152 } |
| 153 // IndexedDB doesn't support this, so neither does localforage. |
| 154 chrome.caterpillar.setError('getBytesInUse not implemented.'); |
| 155 callback(); |
| 156 } |
| 157 |
| 158 /** |
| 159 * Sets multiple items. |
| 160 * |
| 161 * @param {object} items An object which gives key/value pairs to update |
| 162 * storage with. Other key/value pairs in storage will not be affected. |
| 163 * @param {function=} opt_callback Callback on success or failure. |
| 164 */ |
| 165 set(items, opt_callback) { |
| 166 var keys = Object.keys(items); |
| 167 try { |
| 168 // We need to trigger an event containing all the old values and new value
s. |
| 169 // To do that, we first need the old values. |
| 170 this.get(keys, function (oldItems) { |
| 171 // Now that we have the old values, we can set the new values. |
| 172 Promise.all(keys.map(key => localforage.setItem(key, items[key]))) |
| 173 // Then setup the input and trigger the event. |
| 174 .then(function() { |
| 175 var changes = {}; |
| 176 for (var key of keys) { |
| 177 changes[key] = new chrome.storage.StorageChange( |
| 178 oldItems[key], items[key]); |
| 179 } |
| 180 triggerOnChanged(changes); |
| 181 if (opt_callback) |
| 182 opt_callback(); |
| 183 }); |
| 184 }); |
| 185 } catch (e) { |
| 186 chrome.caterpillar.setError('Error setting values: ' + (e.message || e)); |
| 187 if (opt_callback) |
| 188 opt_callback(); |
| 189 } |
| 190 } |
| 191 |
| 192 /** |
| 193 * Removes one or more items from storage. |
| 194 * |
| 195 * @param {string || string[]} keys A single key or a list of keys to |
| 196 * remove. |
| 197 * @param {function=} opt_callback Callback on success or failure. |
| 198 */ |
| 199 remove(keys, opt_callback) { |
| 200 var handleError = function(err) { |
| 201 chrome.caterpillar.setError('Error removing keys: ' + err); |
| 202 if (opt_callback) |
| 203 opt_callback(); |
| 204 }; |
| 205 try { |
| 206 this.get(keys, function(items) { |
| 207 if (typeof keys === 'string') |
| 208 keys = [keys]; |
| 209 |
| 210 Promise.all(keys.map(key => localforage.removeItem(key))) |
| 211 .then(function() { |
| 212 var changes = {}; |
| 213 for (var key of keys) { |
| 214 changes[key] = new chrome.storage.StorageChange( |
| 215 items[key], null); |
| 216 } |
| 217 triggerOnChanged(changes); |
| 218 if (opt_callback) |
| 219 opt_callback(); |
| 220 }) |
| 221 }); |
| 222 } catch (e) { |
| 223 handleError(e.message || e); |
| 224 } |
| 225 } |
| 226 |
| 227 /** |
| 228 * Removes all items from storage. |
| 229 * |
| 230 * @param {function=} opt_callback Callback on success or failure. |
| 231 */ |
| 232 clear(opt_callback) { |
| 233 try { |
| 234 this.get(function(items) { |
| 235 var changes = {}; |
| 236 for (var key in items) { |
| 237 changes[key] = new chrome.storage.StorageChange(items[key], null); |
| 238 } |
| 239 localforage.clear().then(function() { |
| 240 triggerOnChanged(changes); |
| 241 if (opt_callback) |
| 242 opt_callback(); |
| 243 }); |
| 244 }); |
| 245 } catch (e) { |
| 246 chrome.caterpillar.setError('Error clearing values: ' + (e.message || e)); |
| 247 if (opt_callback) |
| 248 opt_callback(); |
| 249 } |
| 250 } |
133 }; | 251 }; |
134 | 252 |
135 /** | 253 /** |
136 * Items in the local storage area are stored locally. | 254 * Items in the local storage area are stored locally. |
137 */ | 255 */ |
138 chrome.storage.local = new chrome.storage.StorageArea(); | 256 chrome.storage.local = new chrome.storage.StorageArea(); |
139 | 257 |
140 /** | 258 /** |
141 * Items in the sync storage area would be synced using Chrome Sync; in this | 259 * Items in the sync storage area would be synced using Chrome Sync; in this |
142 * polyfill we just use local storage. | 260 * polyfill we just use local storage. |
143 */ | 261 */ |
144 chrome.storage.sync = chrome.storage.local; | 262 chrome.storage.sync = chrome.storage.local; |
145 | 263 |
146 /** | 264 /** |
147 * Items in the managed storage area are read-only usually; in this polyfill | 265 * Items in the managed storage area are read-only usually; in this polyfill |
148 * managed is the same as local storage. | 266 * managed is the same as local storage. |
149 */ | 267 */ |
150 chrome.storage.managed = chrome.storage.local; | 268 chrome.storage.managed = chrome.storage.local; |
151 | 269 |
152 /** | 270 /** |
153 * Namespace. | 271 * Namespace. |
154 */ | 272 */ |
155 chrome.storage.onChanged = {}; | 273 chrome.storage.onChanged = {}; |
156 | 274 |
157 /** | 275 /** |
158 * Adds an event listener for the onChanged event. | 276 * Adds an event listener for the onChanged event. |
159 * | 277 * |
160 * @param {function} callback Function taking a changes object of key/value | 278 * @param {function} callback Function taking an object mapping changed keys to |
161 * pairs. | 279 * StorageChanges and an area name (though the latter will always be null). |
162 */ | 280 */ |
163 chrome.storage.onChanged.addListener = function(callback) { | 281 chrome.storage.onChanged.addListener = function(callback) { |
164 self.addEventListener('chrome.storage.onChanged', e => { | 282 var listener = function(e) { |
165 callback(e.detail); | 283 callback(e.detail, null); |
166 }); | 284 }; |
| 285 |
| 286 self.addEventListener('chrome.storage.onChanged', listener); |
| 287 onChangedListeners.push(listener); |
| 288 }; |
| 289 |
| 290 /** |
| 291 * Resets onChanged event listeners. Used for testing. |
| 292 */ |
| 293 chrome.caterpillar.storage.resetOnChangedListenersForTests = function() { |
| 294 for (var listener of onChangedListeners) { |
| 295 self.removeEventListener('chrome.storage.onChanged', listener); |
| 296 } |
| 297 |
| 298 onChangedListeners.length = 0; |
167 }; | 299 }; |
168 | 300 |
169 }).call(this); | 301 }).call(this); |
OLD | NEW |