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