OLD | NEW |
---|---|
1 /* Copyright 2015 The Chromium Authors. All rights reserved. | 1 /* Copyright 2015 The Chromium Authors. All rights reserved. |
2 * Use of this source code is governed by a BSD-style license that can be | 2 * Use of this source code is governed by a BSD-style license that can be |
3 * found in the LICENSE file. */ | 3 * found in the LICENSE file. */ |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * 'settings-prefs' exposes a singleton model of Chrome settings and | 7 * 'settings-prefs' exposes a singleton model of Chrome settings and |
8 * preferences, which listens to changes to Chrome prefs whitelisted in | 8 * preferences, which listens to changes to Chrome prefs whitelisted in |
9 * chrome.settingsPrivate. When changing prefs in this element's 'prefs' | 9 * chrome.settingsPrivate. When changing prefs in this element's 'prefs' |
10 * property via the UI, the singleton model tries to set those preferences in | 10 * property via the UI, the singleton model tries to set those preferences in |
11 * Chrome. Whether or not the calls to settingsPrivate.setPref succeed, 'prefs' | 11 * Chrome. Whether or not the calls to settingsPrivate.setPref succeed, 'prefs' |
12 * is eventually consistent with the Chrome pref store. | 12 * is eventually consistent with the Chrome pref store. |
13 */ | 13 */ |
14 | 14 |
15 (function() { | 15 (function() { |
16 'use strict'; | 16 'use strict'; |
17 | 17 |
18 /** | 18 /** |
19 * Checks whether two values are recursively equal. Only compares serializable | 19 * Checks whether two values are recursively equal. Only compares serializable |
20 * data (primitives, serializable arrays and serializable objects). | 20 * data (primitives, serializable arrays and serializable objects). |
21 * @param {*} val1 Value to compare. | 21 * @param {*} val1 Value to compare. |
22 * @param {*} val2 Value to compare with val1. | 22 * @param {*} val2 Value to compare with val1. |
23 * @return {boolean} True if the values are recursively equal. | 23 * @return {boolean} True if the values are recursively equal. |
24 */ | 24 */ |
25 function deepEqual(val1, val2) { | 25 function deepEqual(val1, val2) { |
26 if (val1 === val2) | 26 if (val1 === val2) |
27 return true; | 27 return true; |
28 | 28 |
29 if (Array.isArray(val1) || Array.isArray(val2)) { | 29 if (Array.isArray(val1) || Array.isArray(val2)) { |
30 if (!Array.isArray(val1) || !Array.isArray(val2)) | 30 if (!Array.isArray(val1) || !Array.isArray(val2)) |
31 return false; | 31 return false; |
32 return arraysEqual(/** @type {!Array} */(val1), | 32 return arraysEqual(/** @type {!Array} */(val1), |
33 /** @type {!Array} */(val2)); | 33 /** @type {!Array} */(val2)); |
34 } | 34 } |
35 | 35 |
36 if (val1 instanceof Object && val2 instanceof Object) | 36 if (val1 instanceof Object && val2 instanceof Object) |
37 return objectsEqual(val1, val2); | 37 return objectsEqual(val1, val2); |
38 | 38 |
39 return false; | 39 return false; |
40 } | 40 } |
41 | 41 |
42 /** | 42 /** |
43 * @param {!Array} arr1 | 43 * @param {!Array} arr1 |
44 * @param {!Array} arr2 | 44 * @param {!Array} arr2 |
45 * @return {boolean} True if the arrays are recursively equal. | 45 * @return {boolean} True if the arrays are recursively equal. |
46 */ | 46 */ |
47 function arraysEqual(arr1, arr2) { | 47 function arraysEqual(arr1, arr2) { |
48 if (arr1.length != arr2.length) | 48 if (arr1.length != arr2.length) |
49 return false; | |
50 | |
51 for (var i = 0; i < arr1.length; i++) { | |
52 if (!deepEqual(arr1[i], arr2[i])) | |
49 return false; | 53 return false; |
54 } | |
50 | 55 |
51 for (var i = 0; i < arr1.length; i++) { | 56 return true; |
52 if (!deepEqual(arr1[i], arr2[i])) | 57 } |
53 return false; | |
54 } | |
55 | |
56 return true; | |
57 } | |
58 | 58 |
59 /** | 59 /** |
60 * @param {!Object} obj1 | 60 * @param {!Object} obj1 |
61 * @param {!Object} obj2 | 61 * @param {!Object} obj2 |
62 * @return {boolean} True if the objects are recursively equal. | 62 * @return {boolean} True if the objects are recursively equal. |
63 */ | 63 */ |
64 function objectsEqual(obj1, obj2) { | 64 function objectsEqual(obj1, obj2) { |
65 var keys1 = Object.keys(obj1); | 65 var keys1 = Object.keys(obj1); |
66 var keys2 = Object.keys(obj2); | 66 var keys2 = Object.keys(obj2); |
67 if (keys1.length != keys2.length) | 67 if (keys1.length != keys2.length) |
68 return false; | |
69 | |
70 for (var i = 0; i < keys1.length; i++) { | |
71 var key = keys1[i]; | |
72 if (!deepEqual(obj1[key], obj2[key])) | |
68 return false; | 73 return false; |
74 } | |
69 | 75 |
70 for (var i = 0; i < keys1.length; i++) { | 76 return true; |
71 var key = keys1[i]; | 77 } |
72 if (!deepEqual(obj1[key], obj2[key])) | |
73 return false; | |
74 } | |
75 | |
76 return true; | |
77 } | |
78 | 78 |
79 /** | 79 /** |
80 * Returns a recursive copy of the value. | 80 * Returns a recursive copy of the value. |
81 * @param {*} val Value to copy. Should be a primitive or only contain | 81 * @param {*} val Value to copy. Should be a primitive or only contain |
82 * serializable data (primitives, serializable arrays and | 82 * serializable data (primitives, serializable arrays and |
83 * serializable objects). | 83 * serializable objects). |
84 * @return {*} A deep copy of the value. | 84 * @return {*} A deep copy of the value. |
85 */ | 85 */ |
86 function deepCopy(val) { | 86 function deepCopy(val) { |
87 if (!(val instanceof Object)) | 87 if (!(val instanceof Object)) |
88 return val; | 88 return val; |
89 return Array.isArray(val) ? deepCopyArray(/** @type {!Array} */(val)) : | 89 return Array.isArray(val) ? deepCopyArray(/** @type {!Array} */(val)) : |
90 deepCopyObject(val); | 90 deepCopyObject(val); |
91 }; | 91 } |
Dan Beam
2017/04/19 19:04:40
did you do this or did eslint --fix do this?
dpapad
2017/04/19 19:19:55
I didn't do any manual text editing in this CL. Th
dpapad
2017/05/05 18:20:46
So this was done because of http://eslint.org/docs
| |
92 | 92 |
93 /** | 93 /** |
94 * @param {!Array} arr | 94 * @param {!Array} arr |
95 * @return {!Array} Deep copy of the array. | 95 * @return {!Array} Deep copy of the array. |
96 */ | 96 */ |
97 function deepCopyArray(arr) { | 97 function deepCopyArray(arr) { |
98 var copy = []; | 98 var copy = []; |
99 for (var i = 0; i < arr.length; i++) | 99 for (var i = 0; i < arr.length; i++) |
100 copy.push(deepCopy(arr[i])); | 100 copy.push(deepCopy(arr[i])); |
101 return copy; | 101 return copy; |
102 } | 102 } |
103 | 103 |
104 /** | 104 /** |
105 * @param {!Object} obj | 105 * @param {!Object} obj |
106 * @return {!Object} Deep copy of the object. | 106 * @return {!Object} Deep copy of the object. |
107 */ | 107 */ |
108 function deepCopyObject(obj) { | 108 function deepCopyObject(obj) { |
109 var copy = {}; | 109 var copy = {}; |
110 var keys = Object.keys(obj); | 110 var keys = Object.keys(obj); |
111 for (var i = 0; i < keys.length; i++) { | 111 for (var i = 0; i < keys.length; i++) { |
112 var key = keys[i]; | 112 var key = keys[i]; |
113 copy[key] = deepCopy(obj[key]); | 113 copy[key] = deepCopy(obj[key]); |
114 } | |
115 return copy; | |
116 } | 114 } |
115 return copy; | |
116 } | |
117 | 117 |
118 Polymer({ | 118 Polymer({ |
119 is: 'settings-prefs', | 119 is: 'settings-prefs', |
120 | 120 |
121 properties: { | 121 properties: { |
122 /** | 122 /** |
123 * Object containing all preferences, for use by Polymer controls. | 123 * Object containing all preferences, for use by Polymer controls. |
124 * @type {Object|undefined} | 124 * @type {Object|undefined} |
125 */ | 125 */ |
126 prefs: { | 126 prefs: { |
127 type: Object, | 127 type: Object, |
128 notify: true, | 128 notify: true, |
129 }, | 129 }, |
130 | 130 |
131 /** | 131 /** |
132 * Map of pref keys to values representing the state of the Chrome | 132 * Map of pref keys to values representing the state of the Chrome |
133 * pref store as of the last update from the API. | 133 * pref store as of the last update from the API. |
134 * @type {Object<*>} | 134 * @type {Object<*>} |
135 * @private | 135 * @private |
136 */ | 136 */ |
137 lastPrefValues_: { | 137 lastPrefValues_: { |
138 type: Object, | 138 type: Object, |
139 value: function() { return {}; }, | 139 value: function() { return {}; }, |
140 }, | |
141 }, | 140 }, |
141 }, | |
142 | 142 |
143 observers: [ | 143 observers: [ |
144 'prefsChanged_(prefs.*)', | 144 'prefsChanged_(prefs.*)', |
145 ], | 145 ], |
146 | 146 |
147 /** @type {SettingsPrivate} */ | 147 /** @type {SettingsPrivate} */ |
148 settingsApi_: /** @type {SettingsPrivate} */(chrome.settingsPrivate), | 148 settingsApi_: /** @type {SettingsPrivate} */(chrome.settingsPrivate), |
149 | 149 |
150 /** @override */ | 150 /** @override */ |
151 created: function() { | 151 created: function() { |
152 if (!CrSettingsPrefs.deferInitialization) | 152 if (!CrSettingsPrefs.deferInitialization) |
153 this.initialize(); | 153 this.initialize(); |
154 }, | 154 }, |
155 | 155 |
156 /** @override */ | 156 /** @override */ |
157 detached: function() { | 157 detached: function() { |
158 CrSettingsPrefs.resetForTesting(); | 158 CrSettingsPrefs.resetForTesting(); |
159 }, | 159 }, |
160 | 160 |
161 /** | 161 /** |
162 * @param {SettingsPrivate=} opt_settingsApi SettingsPrivate implementation | 162 * @param {SettingsPrivate=} opt_settingsApi SettingsPrivate implementation |
163 * to use (chrome.settingsPrivate by default). | 163 * to use (chrome.settingsPrivate by default). |
164 */ | 164 */ |
165 initialize: function(opt_settingsApi) { | 165 initialize: function(opt_settingsApi) { |
166 // Only initialize once (or after resetForTesting() is called). | 166 // Only initialize once (or after resetForTesting() is called). |
167 if (this.initialized_) | 167 if (this.initialized_) |
168 return; | 168 return; |
169 this.initialized_ = true; | 169 this.initialized_ = true; |
170 | 170 |
171 if (opt_settingsApi) | 171 if (opt_settingsApi) |
172 this.settingsApi_ = opt_settingsApi; | 172 this.settingsApi_ = opt_settingsApi; |
173 | 173 |
174 /** @private {function(!Array<!chrome.settingsPrivate.PrefObject>)} */ | 174 /** @private {function(!Array<!chrome.settingsPrivate.PrefObject>)} */ |
175 this.boundPrefsChanged_ = this.onSettingsPrivatePrefsChanged_.bind(this); | 175 this.boundPrefsChanged_ = this.onSettingsPrivatePrefsChanged_.bind(this); |
176 this.settingsApi_.onPrefsChanged.addListener(this.boundPrefsChanged_); | 176 this.settingsApi_.onPrefsChanged.addListener(this.boundPrefsChanged_); |
177 this.settingsApi_.getAllPrefs( | 177 this.settingsApi_.getAllPrefs( |
178 this.onSettingsPrivatePrefsFetched_.bind(this)); | 178 this.onSettingsPrivatePrefsFetched_.bind(this)); |
179 }, | 179 }, |
180 | 180 |
181 /** | 181 /** |
182 * @param {!{path: string}} e | 182 * @param {!{path: string}} e |
183 * @private | 183 * @private |
184 */ | 184 */ |
185 prefsChanged_: function(e) { | 185 prefsChanged_: function(e) { |
186 // |prefs| can be directly set or unset in tests. | 186 // |prefs| can be directly set or unset in tests. |
187 if (!CrSettingsPrefs.isInitialized || e.path == 'prefs') | 187 if (!CrSettingsPrefs.isInitialized || e.path == 'prefs') |
188 return; | 188 return; |
189 | 189 |
190 var key = this.getPrefKeyFromPath_(e.path); | 190 var key = this.getPrefKeyFromPath_(e.path); |
191 var prefStoreValue = this.lastPrefValues_[key]; | 191 var prefStoreValue = this.lastPrefValues_[key]; |
192 | 192 |
193 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | 193 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( |
194 this.get(key, this.prefs)); | 194 this.get(key, this.prefs)); |
195 | 195 |
196 // If settingsPrivate already has this value, ignore it. (Otherwise, | 196 // If settingsPrivate already has this value, ignore it. (Otherwise, |
197 // a change event from settingsPrivate could make us call | 197 // a change event from settingsPrivate could make us call |
198 // settingsPrivate.setPref and potentially trigger an IPC loop.) | 198 // settingsPrivate.setPref and potentially trigger an IPC loop.) |
199 if (!deepEqual(prefStoreValue, prefObj.value)) { | 199 if (!deepEqual(prefStoreValue, prefObj.value)) { |
200 this.settingsApi_.setPref( | 200 this.settingsApi_.setPref( |
201 key, | 201 key, |
202 prefObj.value, | 202 prefObj.value, |
203 /* pageId */ '', | 203 /* pageId */ '', |
204 /* callback */ this.setPrefCallback_.bind(this, key)); | 204 /* callback */ this.setPrefCallback_.bind(this, key)); |
205 } | 205 } |
206 }, | 206 }, |
207 | 207 |
208 /** | 208 /** |
209 * Called when prefs in the underlying Chrome pref store are changed. | 209 * Called when prefs in the underlying Chrome pref store are changed. |
210 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs | 210 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
211 * The prefs that changed. | 211 * The prefs that changed. |
212 * @private | 212 * @private |
213 */ | 213 */ |
214 onSettingsPrivatePrefsChanged_: function(prefs) { | 214 onSettingsPrivatePrefsChanged_: function(prefs) { |
215 if (CrSettingsPrefs.isInitialized) | 215 if (CrSettingsPrefs.isInitialized) |
216 this.updatePrefs_(prefs); | 216 this.updatePrefs_(prefs); |
217 }, | 217 }, |
218 | 218 |
219 /** | 219 /** |
220 * Called when prefs are fetched from settingsPrivate. | 220 * Called when prefs are fetched from settingsPrivate. |
221 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs | 221 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
222 * @private | 222 * @private |
223 */ | 223 */ |
224 onSettingsPrivatePrefsFetched_: function(prefs) { | 224 onSettingsPrivatePrefsFetched_: function(prefs) { |
225 this.updatePrefs_(prefs); | 225 this.updatePrefs_(prefs); |
226 CrSettingsPrefs.setInitialized(); | 226 CrSettingsPrefs.setInitialized(); |
227 }, | 227 }, |
228 | 228 |
229 /** | 229 /** |
230 * Checks the result of calling settingsPrivate.setPref. | 230 * Checks the result of calling settingsPrivate.setPref. |
231 * @param {string} key The key used in the call to setPref. | 231 * @param {string} key The key used in the call to setPref. |
232 * @param {boolean} success True if setting the pref succeeded. | 232 * @param {boolean} success True if setting the pref succeeded. |
233 * @private | 233 * @private |
234 */ | 234 */ |
235 setPrefCallback_: function(key, success) { | 235 setPrefCallback_: function(key, success) { |
236 if (!success) | 236 if (!success) |
237 this.refresh(key); | 237 this.refresh(key); |
238 }, | 238 }, |
239 | 239 |
240 /** | 240 /** |
241 * Get the current pref value from chrome.settingsPrivate to ensure the UI | 241 * Get the current pref value from chrome.settingsPrivate to ensure the UI |
242 * stays up to date. | 242 * stays up to date. |
243 * @param {string} key | 243 * @param {string} key |
244 */ | 244 */ |
245 refresh: function(key) { | 245 refresh: function(key) { |
246 this.settingsApi_.getPref(key, function(pref) { | 246 this.settingsApi_.getPref(key, function(pref) { |
247 this.updatePrefs_([pref]); | 247 this.updatePrefs_([pref]); |
248 }.bind(this)); | 248 }.bind(this)); |
249 }, | 249 }, |
250 | 250 |
251 /** | 251 /** |
252 * Updates the prefs model with the given prefs. | 252 * Updates the prefs model with the given prefs. |
253 * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs | 253 * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs |
254 * @private | 254 * @private |
255 */ | 255 */ |
256 updatePrefs_: function(newPrefs) { | 256 updatePrefs_: function(newPrefs) { |
257 // Use the existing prefs object or create it. | 257 // Use the existing prefs object or create it. |
258 var prefs = this.prefs || {}; | 258 var prefs = this.prefs || {}; |
259 newPrefs.forEach(function(newPrefObj) { | 259 newPrefs.forEach(function(newPrefObj) { |
260 // Use the PrefObject from settingsPrivate to create a copy in | 260 // Use the PrefObject from settingsPrivate to create a copy in |
261 // lastPrefValues_ at the pref's key. | 261 // lastPrefValues_ at the pref's key. |
262 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); | 262 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); |
263 | 263 |
264 if (!deepEqual(this.get(newPrefObj.key, prefs), newPrefObj)) { | 264 if (!deepEqual(this.get(newPrefObj.key, prefs), newPrefObj)) { |
265 // Add the pref to |prefs|. | 265 // Add the pref to |prefs|. |
266 cr.exportPath(newPrefObj.key, newPrefObj, prefs); | 266 cr.exportPath(newPrefObj.key, newPrefObj, prefs); |
267 // If this.prefs already exists, notify listeners of the change. | 267 // If this.prefs already exists, notify listeners of the change. |
268 if (prefs == this.prefs) | 268 if (prefs == this.prefs) |
269 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); | 269 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); |
270 } | 270 } |
271 }, this); | 271 }, this); |
272 if (!this.prefs) | 272 if (!this.prefs) |
273 this.prefs = prefs; | 273 this.prefs = prefs; |
274 }, | 274 }, |
275 | 275 |
276 /** | 276 /** |
277 * Given a 'property-changed' path, returns the key of the preference the | 277 * Given a 'property-changed' path, returns the key of the preference the |
278 * path refers to. E.g., if the path of the changed property is | 278 * path refers to. E.g., if the path of the changed property is |
279 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is | 279 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is |
280 * 'search.suggest_enabled'. | 280 * 'search.suggest_enabled'. |
281 * @param {string} path | 281 * @param {string} path |
282 * @return {string} | 282 * @return {string} |
283 * @private | 283 * @private |
284 */ | 284 */ |
285 getPrefKeyFromPath_: function(path) { | 285 getPrefKeyFromPath_: function(path) { |
286 // Skip the first token, which refers to the member variable (this.prefs). | 286 // Skip the first token, which refers to the member variable (this.prefs). |
287 var parts = path.split('.'); | 287 var parts = path.split('.'); |
288 assert(parts.shift() == 'prefs', "Path doesn't begin with 'prefs'"); | 288 assert(parts.shift() == 'prefs', 'Path doesn\'t begin with \'prefs\''); |
dschuyler
2017/04/19 18:58:41
I find it more readable to use double quotes in th
Dan Beam
2017/04/19 19:07:03
+1, not sure what clang-format does here
not the
| |
289 | 289 |
290 for (var i = 1; i <= parts.length; i++) { | 290 for (var i = 1; i <= parts.length; i++) { |
291 var key = parts.slice(0, i).join('.'); | 291 var key = parts.slice(0, i).join('.'); |
292 // The lastPrefValues_ keys match the pref keys. | 292 // The lastPrefValues_ keys match the pref keys. |
293 if (this.lastPrefValues_.hasOwnProperty(key)) | 293 if (this.lastPrefValues_.hasOwnProperty(key)) |
294 return key; | 294 return key; |
295 } | 295 } |
296 return ''; | 296 return ''; |
297 }, | 297 }, |
298 | 298 |
299 /** | 299 /** |
300 * Resets the element so it can be re-initialized with a new prefs state. | 300 * Resets the element so it can be re-initialized with a new prefs state. |
301 */ | 301 */ |
302 resetForTesting: function() { | 302 resetForTesting: function() { |
303 if (!this.initialized_) | 303 if (!this.initialized_) |
304 return; | 304 return; |
305 this.prefs = undefined; | 305 this.prefs = undefined; |
306 this.lastPrefValues_ = {}; | 306 this.lastPrefValues_ = {}; |
307 this.initialized_ = false; | 307 this.initialized_ = false; |
308 // Remove the listener added in initialize(). | 308 // Remove the listener added in initialize(). |
309 this.settingsApi_.onPrefsChanged.removeListener(this.boundPrefsChanged_); | 309 this.settingsApi_.onPrefsChanged.removeListener(this.boundPrefsChanged_); |
310 this.settingsApi_ = | 310 this.settingsApi_ = |
311 /** @type {SettingsPrivate} */(chrome.settingsPrivate); | 311 /** @type {SettingsPrivate} */(chrome.settingsPrivate); |
312 }, | 312 }, |
313 }); | 313 }); |
314 })(); | 314 })(); |
OLD | NEW |