| 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 |