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 * 'cr-settings-prefs' models Chrome settings and preferences, listening for | 7 * 'cr-settings-prefs' models Chrome settings and preferences, listening for |
8 * changes to Chrome prefs whitelisted in chrome.settingsPrivate. | 8 * changes to Chrome prefs whitelisted in chrome.settingsPrivate. |
9 * When changing prefs in this element's 'prefs' property via the UI, this | 9 * When changing prefs in this element's 'prefs' property via the UI, this |
10 * element tries to set those preferences in Chrome. Whether or not the calls to | 10 * element tries to set those preferences in Chrome. Whether or not the calls to |
11 * settingsPrivate.setPref succeed, 'prefs' is eventually consistent with the | 11 * settingsPrivate.setPref succeed, 'prefs' is eventually consistent with the |
12 * Chrome pref store. | 12 * Chrome pref store. |
13 * | 13 * |
14 * Example: | 14 * Example: |
15 * | 15 * |
16 * <cr-settings-prefs prefs="{{prefs}}"></cr-settings-prefs> | 16 * <cr-settings-prefs prefs="{{prefs}}"></cr-settings-prefs> |
17 * <cr-settings-a11y-page prefs="{{prefs}}"></cr-settings-a11y-page> | 17 * <cr-settings-a11y-page prefs="{{prefs}}"></cr-settings-a11y-page> |
18 * | 18 * |
19 * @group Chrome Settings Elements | 19 * @group Chrome Settings Elements |
20 * @element cr-settings-prefs | 20 * @element cr-settings-prefs |
21 */ | 21 */ |
22 | 22 |
23 /** | |
24 * Pref state object. Copies values of PrefObjects received from | |
25 * settingsPrivate to determine when pref values have changed. | |
26 * This prototype works for primitive types, but more complex types should | |
27 * override these functions. | |
28 * @constructor | |
29 * @param {!chrome.settingsPrivate.PrefObject} prefObj | |
30 */ | |
31 function PrefWrapper(prefObj) { | |
32 this.key_ = prefObj.key; | |
33 this.value_ = prefObj.value; | |
34 } | |
35 | |
36 /** | |
37 * Checks if other's value equals this.value_. Both prefs wrapped by the | |
38 * PrefWrappers should have the same keys. | |
39 * @param {PrefWrapper} other | |
40 * @return {boolean} | |
41 */ | |
42 PrefWrapper.prototype.equals = function(other) { | |
43 assert(this.key_ == other.key_); | |
44 return this.value_ == other.value_; | |
45 }; | |
46 | |
47 /** | |
48 * @constructor | |
49 * @extends {PrefWrapper} | |
50 * @param {!chrome.settingsPrivate.PrefObject} prefObj | |
51 */ | |
52 function ListPrefWrapper(prefObj) { | |
53 // Copy the array so changes to prefObj aren't reflected in this.value_. | |
54 // TODO(michaelpg): Do a deep copy to support nested lists and objects. | |
55 this.value_ = prefObj.value.slice(); | |
56 } | |
57 | |
58 ListPrefWrapper.prototype = { | |
59 __proto__: PrefWrapper.prototype, | |
60 | |
61 /** | |
62 * Tests whether two ListPrefWrapper values contain the same list items. | |
63 * @override | |
64 */ | |
65 equals: function(other) { | |
66 'use strict'; | |
67 assert(this.key_ == other.key_); | |
68 if (this.value_.length != other.value_.length) | |
69 return false; | |
70 for (let i = 0; i < this.value_.length; i++) { | |
71 if (this.value_[i] != other.value_[i]) | |
72 return false; | |
73 } | |
74 return true; | |
75 }, | |
76 }; | |
77 | |
78 (function() { | 23 (function() { |
79 'use strict'; | 24 'use strict'; |
80 | 25 |
26 /** | |
27 * Checks whether two values are recursively equal. Only compares serializable | |
28 * data (primitives, serializable arrays and serializable objects). | |
29 * @param {*} val1 Value to compare. | |
30 * @param {*} val2 Value to compare with val1. | |
31 * @return {boolean} True if the values are recursively equal. | |
32 */ | |
33 function deepEqual(val1, val2) { | |
34 if (val1 === val2) | |
35 return true; | |
Dan Beam
2015/09/17 23:12:01
needs moar \n
michaelpg
2015/09/18 01:47:12
Done.
| |
36 if (val1 == null || val2 == null) | |
37 return false; | |
38 if (Array.isArray(val1) || Array.isArray(val2)) { | |
39 if (!Array.isArray(val1) || !Array.isArray(val2)) | |
40 return false; | |
41 return arraysEqual(/** @type {!Array} */(val1), | |
42 /** @type {!Array} */(val2)); | |
43 } | |
44 if (val1 instanceof Object && val2 instanceof Object) | |
45 return objectsEqual(val1, val2); | |
46 return false; | |
47 } | |
48 | |
49 /** | |
50 * @param {!Array} arr1 | |
51 * @param {!Array} arr2 | |
52 * @return {boolean} True if the arrays are recursively equal. | |
53 */ | |
54 function arraysEqual(arr1, arr2) { | |
55 if (arr1.length != arr2.length) | |
56 return false; | |
57 for (var i = 0; i < arr1.length; i++) { | |
58 if (!deepEqual(arr1[i], arr2[i])) | |
59 return false; | |
60 } | |
61 return true; | |
62 } | |
63 | |
64 /** | |
65 * @param {!Object} obj1 | |
66 * @param {!Object} obj2 | |
67 * @return {boolean} True if the objects are recursively equal. | |
68 */ | |
69 function objectsEqual(obj1, obj2) { | |
70 var keys1 = Object.keys(obj1); | |
71 var keys2 = Object.keys(obj2); | |
Dan Beam
2015/09/17 23:12:01
do we care about non-own keys?
michaelpg
2015/09/18 01:47:12
No, everything coming from the pref store should b
| |
72 if (keys1.length != keys2.length) | |
73 return false; | |
74 for (var i = 0; i < keys1.length; i++) { | |
75 var key = keys1[i]; | |
76 if (!deepEqual(obj1[key], obj2[key])) | |
77 return false; | |
78 } | |
79 return true; | |
80 } | |
81 | |
82 /** | |
83 * Returns a recursive copy of the value. | |
84 * @param {*} val Value to copy. Should be a primitive or only contain | |
85 * serializable data (primitives, serializable arrays and | |
86 * serializable objects). | |
87 * @return {*} A deep copy of the value. | |
88 */ | |
89 function deepCopy(val) { | |
90 if (val instanceof Object) { | |
91 if (Array.isArray(val)) | |
92 return deepCopyArray(/** @type {!Array} */(val)); | |
93 return deepCopyObject(val); | |
94 } | |
95 return val; | |
Dan Beam
2015/09/17 23:12:01
nit:
if (!(val instanceof Object))
return val;
michaelpg
2015/09/18 01:47:12
Done.
| |
96 }; | |
97 | |
98 /** | |
99 * @param {!Array} arr | |
100 * @return {!Array} Deep copy of the array. | |
101 */ | |
102 function deepCopyArray(arr) { | |
103 var copy = []; | |
104 for (var i = 0; i < arr.length; i++) | |
105 copy.push(deepCopy(arr[i])); | |
106 return copy; | |
107 } | |
108 | |
109 /** | |
110 * @param {!Object} obj | |
111 * @return {!Object} Deep copy of the object. | |
112 */ | |
113 function deepCopyObject(obj) { | |
114 var copy = {}; | |
115 var keys = Object.keys(obj); | |
116 for (var i = 0; i < keys.length; i++) { | |
117 var key = keys[i]; | |
118 copy[key] = deepCopy(obj[key]); | |
119 } | |
120 return copy; | |
121 } | |
122 | |
81 Polymer({ | 123 Polymer({ |
82 is: 'cr-settings-prefs', | 124 is: 'cr-settings-prefs', |
83 | 125 |
84 properties: { | 126 properties: { |
85 /** | 127 /** |
86 * Object containing all preferences, for use by Polymer controls. | 128 * Object containing all preferences, for use by Polymer controls. |
87 */ | 129 */ |
88 prefs: { | 130 prefs: { |
89 type: Object, | 131 type: Object, |
90 notify: true, | 132 notify: true, |
91 }, | 133 }, |
92 | 134 |
93 /** | 135 /** |
94 * Map of pref keys to PrefWrapper objects representing the state of the | 136 * Map of pref keys to values representing the state of the Chrome |
95 * Chrome pref store. | 137 * pref store as of the last update from the API. |
96 * @type {Object<PrefWrapper>} | 138 * @type {Object<*>} |
97 * @private | 139 * @private |
98 */ | 140 */ |
99 prefWrappers_: { | 141 lastPrefValues_: { |
100 type: Object, | 142 type: Object, |
101 value: function() { return {}; }, | 143 value: function() { return {}; }, |
102 }, | 144 }, |
103 }, | 145 }, |
104 | 146 |
105 observers: [ | 147 observers: [ |
106 'prefsChanged_(prefs.*)', | 148 'prefsChanged_(prefs.*)', |
107 ], | 149 ], |
108 | 150 |
109 settingsApi_: chrome.settingsPrivate, | 151 settingsApi_: chrome.settingsPrivate, |
(...skipping 15 matching lines...) Expand all Loading... | |
125 /** | 167 /** |
126 * Polymer callback for changes to this.prefs. | 168 * Polymer callback for changes to this.prefs. |
127 * @param {!{path: string, value: *}} change | 169 * @param {!{path: string, value: *}} change |
128 * @private | 170 * @private |
129 */ | 171 */ |
130 prefsChanged_: function(change) { | 172 prefsChanged_: function(change) { |
131 if (!CrSettingsPrefs.isInitialized) | 173 if (!CrSettingsPrefs.isInitialized) |
132 return; | 174 return; |
133 | 175 |
134 var key = this.getPrefKeyFromPath_(change.path); | 176 var key = this.getPrefKeyFromPath_(change.path); |
135 var prefWrapper = this.prefWrappers_[key]; | 177 var prefStoreValue = this.lastPrefValues_[key]; |
136 if (!prefWrapper) | |
137 return; | |
138 | 178 |
139 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | 179 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( |
140 this.get(key, this.prefs)); | 180 this.get(key, this.prefs)); |
141 | 181 |
142 // If settingsPrivate already has this value, do nothing. (Otherwise, | 182 // If settingsPrivate already has this value, do nothing. (Otherwise, |
143 // a change event from settingsPrivate could make us call | 183 // a change event from settingsPrivate could make us call |
144 // settingsPrivate.setPref and potentially trigger an IPC loop.) | 184 // settingsPrivate.setPref and potentially trigger an IPC loop.) |
145 if (prefWrapper.equals(this.createPrefWrapper_(prefObj))) | 185 if (deepEqual(prefStoreValue, prefObj.value)) |
146 return; | 186 return; |
147 | 187 |
148 this.settingsApi_.setPref( | 188 this.settingsApi_.setPref( |
149 key, | 189 key, |
150 prefObj.value, | 190 prefObj.value, |
151 /* pageId */ '', | 191 /* pageId */ '', |
152 /* callback */ this.setPrefCallback_.bind(this, key)); | 192 /* callback */ this.setPrefCallback_.bind(this, key)); |
153 }, | 193 }, |
154 | 194 |
155 /** | 195 /** |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
194 | 234 |
195 /** | 235 /** |
196 * Updates the prefs model with the given prefs. | 236 * Updates the prefs model with the given prefs. |
197 * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs | 237 * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs |
198 * @private | 238 * @private |
199 */ | 239 */ |
200 updatePrefs_: function(newPrefs) { | 240 updatePrefs_: function(newPrefs) { |
201 // Use the existing prefs object or create it. | 241 // Use the existing prefs object or create it. |
202 var prefs = this.prefs || {}; | 242 var prefs = this.prefs || {}; |
203 newPrefs.forEach(function(newPrefObj) { | 243 newPrefs.forEach(function(newPrefObj) { |
204 // Use the PrefObject from settingsPrivate to create a PrefWrapper in | 244 // Use the PrefObject from settingsPrivate to create a copy in |
205 // prefWrappers_ at the pref's key. | 245 // lastPrefValues_ at the pref's key. |
206 this.prefWrappers_[newPrefObj.key] = | 246 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); |
207 this.createPrefWrapper_(newPrefObj); | |
208 | 247 |
209 // Add the pref to |prefs|. | 248 // Add the pref to |prefs|. |
210 cr.exportPath(newPrefObj.key, newPrefObj, prefs); | 249 cr.exportPath(newPrefObj.key, newPrefObj, prefs); |
211 // If this.prefs already exists, notify listeners of the change. | 250 // If this.prefs already exists, notify listeners of the change. |
212 if (prefs == this.prefs) | 251 if (prefs == this.prefs) |
213 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); | 252 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); |
214 }, this); | 253 }, this); |
215 if (!this.prefs) | 254 if (!this.prefs) |
216 this.prefs = prefs; | 255 this.prefs = prefs; |
217 }, | 256 }, |
218 | 257 |
219 /** | 258 /** |
220 * Given a 'property-changed' path, returns the key of the preference the | 259 * Given a 'property-changed' path, returns the key of the preference the |
221 * path refers to. E.g., if the path of the changed property is | 260 * path refers to. E.g., if the path of the changed property is |
222 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is | 261 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is |
223 * 'search.suggest_enabled'. | 262 * 'search.suggest_enabled'. |
224 * @param {string} path | 263 * @param {string} path |
225 * @return {string} | 264 * @return {string} |
226 * @private | 265 * @private |
227 */ | 266 */ |
228 getPrefKeyFromPath_: function(path) { | 267 getPrefKeyFromPath_: function(path) { |
229 // Skip the first token, which refers to the member variable (this.prefs). | 268 // Skip the first token, which refers to the member variable (this.prefs). |
230 var parts = path.split('.'); | 269 var parts = path.split('.'); |
231 assert(parts.shift() == 'prefs'); | 270 assert(parts.shift() == 'prefs'); |
232 | 271 |
233 for (let i = 1; i <= parts.length; i++) { | 272 for (let i = 1; i <= parts.length; i++) { |
234 let key = parts.slice(0, i).join('.'); | 273 let key = parts.slice(0, i).join('.'); |
235 // The prefWrappers_ keys match the pref keys. | 274 // The lastPrefValues_ keys match the pref keys. |
236 if (this.prefWrappers_[key] != undefined) | 275 if (this.lastPrefValues_.hasOwnProperty(key)) |
237 return key; | 276 return key; |
238 } | 277 } |
239 return ''; | 278 return ''; |
240 }, | 279 }, |
241 | |
242 /** | |
243 * Creates a PrefWrapper object from a chrome.settingsPrivate pref. | |
244 * @param {!chrome.settingsPrivate.PrefObject} prefObj | |
245 * PrefObject received from chrome.settingsPrivate. | |
246 * @return {PrefWrapper} An object containing a copy of the PrefObject's | |
247 * value. | |
248 * @private | |
249 */ | |
250 createPrefWrapper_: function(prefObj) { | |
251 return prefObj.type == chrome.settingsPrivate.PrefType.LIST ? | |
252 new ListPrefWrapper(prefObj) : new PrefWrapper(prefObj); | |
253 }, | |
254 }); | 280 }); |
255 })(); | 281 })(); |
OLD | NEW |