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; | |
36 | |
37 if (val1 == null || val2 == null) | |
Dan Beam
2015/09/18 01:54:16
you should probably use === just to make sure
michaelpg
2015/09/18 02:03:22
actually this line is superfluous because null and
Dan Beam
2015/09/18 02:05:18
super
| |
38 return false; | |
39 | |
40 if (Array.isArray(val1) || Array.isArray(val2)) { | |
41 if (!Array.isArray(val1) || !Array.isArray(val2)) | |
42 return false; | |
43 return arraysEqual(/** @type {!Array} */(val1), | |
44 /** @type {!Array} */(val2)); | |
45 } | |
46 | |
47 if (val1 instanceof Object && val2 instanceof Object) | |
48 return objectsEqual(val1, val2); | |
49 | |
50 return false; | |
51 } | |
52 | |
53 /** | |
54 * @param {!Array} arr1 | |
55 * @param {!Array} arr2 | |
56 * @return {boolean} True if the arrays are recursively equal. | |
57 */ | |
58 function arraysEqual(arr1, arr2) { | |
59 if (arr1.length != arr2.length) | |
60 return false; | |
61 | |
62 for (var i = 0; i < arr1.length; i++) { | |
63 if (!deepEqual(arr1[i], arr2[i])) | |
64 return false; | |
65 } | |
66 | |
67 return true; | |
68 } | |
69 | |
70 /** | |
71 * @param {!Object} obj1 | |
72 * @param {!Object} obj2 | |
73 * @return {boolean} True if the objects are recursively equal. | |
74 */ | |
75 function objectsEqual(obj1, obj2) { | |
76 var keys1 = Object.keys(obj1); | |
77 var keys2 = Object.keys(obj2); | |
78 if (keys1.length != keys2.length) | |
79 return false; | |
80 | |
81 for (var i = 0; i < keys1.length; i++) { | |
82 var key = keys1[i]; | |
83 if (!deepEqual(obj1[key], obj2[key])) | |
84 return false; | |
85 } | |
86 | |
87 return true; | |
88 } | |
89 | |
90 /** | |
91 * Returns a recursive copy of the value. | |
92 * @param {*} val Value to copy. Should be a primitive or only contain | |
93 * serializable data (primitives, serializable arrays and | |
94 * serializable objects). | |
95 * @return {*} A deep copy of the value. | |
96 */ | |
97 function deepCopy(val) { | |
98 if (!(val instanceof Object)) | |
99 return val; | |
100 return Array.isArray(val) ? deepCopyArray(/** @type {!Array} */(val)) : | |
101 deepCopyObject(val); | |
102 }; | |
103 | |
104 /** | |
105 * @param {!Array} arr | |
106 * @return {!Array} Deep copy of the array. | |
107 */ | |
108 function deepCopyArray(arr) { | |
109 var copy = []; | |
110 for (var i = 0; i < arr.length; i++) | |
111 copy.push(deepCopy(arr[i])); | |
112 return copy; | |
113 } | |
114 | |
115 /** | |
116 * @param {!Object} obj | |
117 * @return {!Object} Deep copy of the object. | |
118 */ | |
119 function deepCopyObject(obj) { | |
120 var copy = {}; | |
121 var keys = Object.keys(obj); | |
122 for (var i = 0; i < keys.length; i++) { | |
123 var key = keys[i]; | |
124 copy[key] = deepCopy(obj[key]); | |
125 } | |
126 return copy; | |
127 } | |
128 | |
81 Polymer({ | 129 Polymer({ |
82 is: 'cr-settings-prefs', | 130 is: 'cr-settings-prefs', |
83 | 131 |
84 properties: { | 132 properties: { |
85 /** | 133 /** |
86 * Object containing all preferences, for use by Polymer controls. | 134 * Object containing all preferences, for use by Polymer controls. |
87 */ | 135 */ |
88 prefs: { | 136 prefs: { |
89 type: Object, | 137 type: Object, |
90 notify: true, | 138 notify: true, |
91 }, | 139 }, |
92 | 140 |
93 /** | 141 /** |
94 * Map of pref keys to PrefWrapper objects representing the state of the | 142 * Map of pref keys to values representing the state of the Chrome |
95 * Chrome pref store. | 143 * pref store as of the last update from the API. |
96 * @type {Object<PrefWrapper>} | 144 * @type {Object<*>} |
97 * @private | 145 * @private |
98 */ | 146 */ |
99 prefWrappers_: { | 147 lastPrefValues_: { |
100 type: Object, | 148 type: Object, |
101 value: function() { return {}; }, | 149 value: function() { return {}; }, |
102 }, | 150 }, |
103 }, | 151 }, |
104 | 152 |
105 observers: [ | 153 observers: [ |
106 'prefsChanged_(prefs.*)', | 154 'prefsChanged_(prefs.*)', |
107 ], | 155 ], |
108 | 156 |
109 settingsApi_: chrome.settingsPrivate, | 157 settingsApi_: chrome.settingsPrivate, |
(...skipping 15 matching lines...) Expand all Loading... | |
125 /** | 173 /** |
126 * Polymer callback for changes to this.prefs. | 174 * Polymer callback for changes to this.prefs. |
127 * @param {!{path: string, value: *}} change | 175 * @param {!{path: string, value: *}} change |
128 * @private | 176 * @private |
129 */ | 177 */ |
130 prefsChanged_: function(change) { | 178 prefsChanged_: function(change) { |
131 if (!CrSettingsPrefs.isInitialized) | 179 if (!CrSettingsPrefs.isInitialized) |
132 return; | 180 return; |
133 | 181 |
134 var key = this.getPrefKeyFromPath_(change.path); | 182 var key = this.getPrefKeyFromPath_(change.path); |
135 var prefWrapper = this.prefWrappers_[key]; | 183 var prefStoreValue = this.lastPrefValues_[key]; |
136 if (!prefWrapper) | |
137 return; | |
138 | 184 |
139 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | 185 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( |
140 this.get(key, this.prefs)); | 186 this.get(key, this.prefs)); |
141 | 187 |
142 // If settingsPrivate already has this value, do nothing. (Otherwise, | 188 // If settingsPrivate already has this value, do nothing. (Otherwise, |
143 // a change event from settingsPrivate could make us call | 189 // a change event from settingsPrivate could make us call |
144 // settingsPrivate.setPref and potentially trigger an IPC loop.) | 190 // settingsPrivate.setPref and potentially trigger an IPC loop.) |
145 if (prefWrapper.equals(this.createPrefWrapper_(prefObj))) | 191 if (deepEqual(prefStoreValue, prefObj.value)) |
146 return; | 192 return; |
147 | 193 |
148 this.settingsApi_.setPref( | 194 this.settingsApi_.setPref( |
149 key, | 195 key, |
150 prefObj.value, | 196 prefObj.value, |
151 /* pageId */ '', | 197 /* pageId */ '', |
152 /* callback */ this.setPrefCallback_.bind(this, key)); | 198 /* callback */ this.setPrefCallback_.bind(this, key)); |
153 }, | 199 }, |
154 | 200 |
155 /** | 201 /** |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
194 | 240 |
195 /** | 241 /** |
196 * Updates the prefs model with the given prefs. | 242 * Updates the prefs model with the given prefs. |
197 * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs | 243 * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs |
198 * @private | 244 * @private |
199 */ | 245 */ |
200 updatePrefs_: function(newPrefs) { | 246 updatePrefs_: function(newPrefs) { |
201 // Use the existing prefs object or create it. | 247 // Use the existing prefs object or create it. |
202 var prefs = this.prefs || {}; | 248 var prefs = this.prefs || {}; |
203 newPrefs.forEach(function(newPrefObj) { | 249 newPrefs.forEach(function(newPrefObj) { |
204 // Use the PrefObject from settingsPrivate to create a PrefWrapper in | 250 // Use the PrefObject from settingsPrivate to create a copy in |
205 // prefWrappers_ at the pref's key. | 251 // lastPrefValues_ at the pref's key. |
206 this.prefWrappers_[newPrefObj.key] = | 252 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); |
207 this.createPrefWrapper_(newPrefObj); | |
208 | 253 |
209 // Add the pref to |prefs|. | 254 // Add the pref to |prefs|. |
210 cr.exportPath(newPrefObj.key, newPrefObj, prefs); | 255 cr.exportPath(newPrefObj.key, newPrefObj, prefs); |
211 // If this.prefs already exists, notify listeners of the change. | 256 // If this.prefs already exists, notify listeners of the change. |
212 if (prefs == this.prefs) | 257 if (prefs == this.prefs) |
213 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); | 258 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); |
214 }, this); | 259 }, this); |
215 if (!this.prefs) | 260 if (!this.prefs) |
216 this.prefs = prefs; | 261 this.prefs = prefs; |
217 }, | 262 }, |
218 | 263 |
219 /** | 264 /** |
220 * Given a 'property-changed' path, returns the key of the preference the | 265 * 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 | 266 * 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 | 267 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is |
223 * 'search.suggest_enabled'. | 268 * 'search.suggest_enabled'. |
224 * @param {string} path | 269 * @param {string} path |
225 * @return {string} | 270 * @return {string} |
226 * @private | 271 * @private |
227 */ | 272 */ |
228 getPrefKeyFromPath_: function(path) { | 273 getPrefKeyFromPath_: function(path) { |
229 // Skip the first token, which refers to the member variable (this.prefs). | 274 // Skip the first token, which refers to the member variable (this.prefs). |
230 var parts = path.split('.'); | 275 var parts = path.split('.'); |
231 assert(parts.shift() == 'prefs'); | 276 assert(parts.shift() == 'prefs'); |
232 | 277 |
233 for (let i = 1; i <= parts.length; i++) { | 278 for (let i = 1; i <= parts.length; i++) { |
234 let key = parts.slice(0, i).join('.'); | 279 let key = parts.slice(0, i).join('.'); |
235 // The prefWrappers_ keys match the pref keys. | 280 // The lastPrefValues_ keys match the pref keys. |
236 if (this.prefWrappers_[key] != undefined) | 281 if (this.lastPrefValues_.hasOwnProperty(key)) |
237 return key; | 282 return key; |
238 } | 283 } |
239 return ''; | 284 return ''; |
240 }, | 285 }, |
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 }); | 286 }); |
255 })(); | 287 })(); |
OLD | NEW |