Chromium Code Reviews| 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' is an element which serves as a model for | 7 * 'cr-settings-prefs' models Chrome settings and preferences, listening for |
| 8 * interaction with settings which are stored in Chrome's | 8 * changes to Chrome prefs whitelisted in chrome.settingsPrivate. |
| 9 * Preferences. | 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 | |
| 11 * settingsPrivate.setPref succeed, 'prefs' is eventually consistent with the | |
| 12 * Chrome pref store. | |
| 10 * | 13 * |
| 11 * Example: | 14 * Example: |
| 12 * | 15 * |
| 13 * <cr-settings-prefs id="prefs"></cr-settings-prefs> | 16 * <cr-settings-prefs prefs="{{prefs}}"></cr-settings-prefs> |
| 14 * <cr-settings-a11y-page prefs="{{this.$.prefs}}"></cr-settings-a11y-page> | 17 * <cr-settings-a11y-page prefs="{{prefs}}"></cr-settings-a11y-page> |
| 15 * | 18 * |
| 16 * @group Chrome Settings Elements | 19 * @group Chrome Settings Elements |
| 17 * @element cr-settings-a11y-page | 20 * @element cr-settings-prefs |
| 18 */ | 21 */ |
| 19 (function() { | 22 (function() { |
| 20 'use strict'; | 23 'use strict'; |
| 21 | 24 |
| 25 /** | |
| 26 * Pref state object. Copies values of PrefObjects received from | |
| 27 * settingsPrivate to determine when pref values have changed. | |
| 28 * This prototype works for primitive types, but more complex types should | |
| 29 * override these functions. | |
| 30 * @constructor | |
| 31 * @param {!chrome.settingsPrivate.PrefObject} prefObj | |
| 32 */ | |
| 33 function PrefWrapper(prefObj) { | |
| 34 this.key_ = prefObj.key; | |
| 35 this.value_ = prefObj.value; | |
| 36 } | |
| 37 | |
| 38 /** | |
| 39 * Checks if other's value equals this.value_. Both prefs wrapped by the | |
| 40 * PrefWrappers should have the same keys. | |
| 41 * @param {PrefWrapper} other | |
| 42 * @return {boolean} | |
| 43 */ | |
| 44 PrefWrapper.prototype.equals = function(other) { | |
| 45 assert(this.key_ == other.key_); | |
| 46 return this.value_ == other.value_; | |
| 47 }; | |
| 48 | |
| 49 /** | |
| 50 * @constructor | |
| 51 * @extends {PrefWrapper} | |
| 52 * @param {!chrome.settingsPrivate.PrefObject} prefObj | |
| 53 */ | |
| 54 function ListPrefWrapper(prefObj) { | |
| 55 // Copy the array so changes to prefObj aren't reflected in this.value_. | |
| 56 // TODO(michaelpg): Do a deep copy to support nested lists and objects. | |
| 57 this.value_ = prefObj.value.slice(); | |
| 58 } | |
| 59 | |
| 60 ListPrefWrapper.prototype = { | |
| 61 __proto__: PrefWrapper.prototype, | |
| 62 | |
| 63 /** | |
| 64 * Tests whether two ListPrefWrapper values contain the same list items. | |
| 65 * @override | |
| 66 */ | |
| 67 equals: function(other) { | |
| 68 assert(this.key_ == other.key_); | |
| 69 if (this.value_.length != other.value_.length) | |
| 70 return false; | |
| 71 for (let i = 0; i < this.value_.length; i++) { | |
| 72 if (this.value_[i] != other.value_[i]) | |
| 73 return false; | |
| 74 } | |
| 75 return true; | |
| 76 }, | |
| 77 }; | |
| 78 | |
| 22 Polymer({ | 79 Polymer({ |
| 23 is: 'cr-settings-prefs', | 80 is: 'cr-settings-prefs', |
| 24 | 81 |
| 25 properties: { | 82 properties: { |
| 26 /** | 83 /** |
| 27 * Object containing all preferences. | 84 * Object containing all preferences, for use by Polymer controls. |
| 28 */ | 85 */ |
| 29 prefStore: { | 86 prefs: { |
| 30 type: Object, | 87 type: Object, |
| 31 value: function() { return {}; }, | 88 value: function() { return {}; }, |
| 32 notify: true, | 89 notify: true, |
| 33 }, | 90 }, |
| 34 }, | 91 |
| 92 /** | |
| 93 * Map of pref keys to PrefWrapper objects representing the state of the | |
| 94 * Chrome pref store. | |
| 95 * @type {Object<PrefWrapper>} | |
|
michaelpg
2015/08/28 20:43:05
"ERROR - Bad type annotation. Unknown type PrefWra
michaelpg
2015/08/29 01:26:00
Done.
| |
| 96 * @private | |
| 97 */ | |
| 98 prefWrappers_: { | |
| 99 type: Object, | |
| 100 value: function() { return {}; }, | |
| 101 }, | |
| 102 }, | |
| 103 | |
| 104 observers: [ | |
| 105 'prefsChanged_(prefs.*)', | |
| 106 ], | |
| 35 | 107 |
| 36 /** @override */ | 108 /** @override */ |
| 37 created: function() { | 109 created: function() { |
| 38 CrSettingsPrefs.isInitialized = false; | 110 CrSettingsPrefs.isInitialized = false; |
| 39 | 111 |
| 40 chrome.settingsPrivate.onPrefsChanged.addListener( | 112 chrome.settingsPrivate.onPrefsChanged.addListener( |
| 41 this.onPrefsChanged_.bind(this)); | 113 this.onSettingsPrivatePrefsChanged_.bind(this)); |
| 42 chrome.settingsPrivate.getAllPrefs(this.onPrefsFetched_.bind(this)); | 114 chrome.settingsPrivate.getAllPrefs( |
| 115 this.onSettingsPrivatePrefsFetched_.bind(this)); | |
| 116 }, | |
| 117 | |
| 118 /** | |
| 119 * Polymer callback for changes to this.prefs. | |
| 120 * @param {!{path: string, value: *}} change | |
| 121 * @private | |
| 122 */ | |
| 123 prefsChanged_: function(change) { | |
| 124 if (!CrSettingsPrefs.isInitialized) | |
| 125 return; | |
| 126 | |
| 127 var key = this.getPrefKeyFromPath_(change.path); | |
| 128 var prefWrapper = this.prefWrappers_[key]; | |
| 129 if (!prefWrapper) | |
| 130 return; | |
| 131 | |
| 132 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | |
| 133 this.get(key, this.prefs)); | |
| 134 | |
| 135 // If settingsPrivate already has this value, do nothing. (Otherwise, | |
| 136 // a change event from settingsPrivate could make us call | |
| 137 // settingsPrivate.setPref and potentially trigger an IPC loop.) | |
| 138 if (prefWrapper.equals(this.createPrefWrapper_(prefObj))) | |
| 139 return; | |
| 140 | |
| 141 chrome.settingsPrivate.setPref( | |
| 142 key, | |
| 143 prefObj.value, | |
| 144 /* pageId */ '', | |
| 145 /* callback */ this.setPrefCallback_.bind(this, key)); | |
| 43 }, | 146 }, |
| 44 | 147 |
| 45 /** | 148 /** |
| 46 * Called when prefs in the underlying Chrome pref store are changed. | 149 * Called when prefs in the underlying Chrome pref store are changed. |
| 47 * @param {!Array<!PrefObject>} prefs The prefs that changed. | 150 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
| 48 * @private | 151 * The prefs that changed. |
| 49 */ | 152 * @private |
| 50 onPrefsChanged_: function(prefs) { | 153 */ |
| 51 this.updatePrefs_(prefs, false); | 154 onSettingsPrivatePrefsChanged_: function(prefs) { |
| 155 if (CrSettingsPrefs.isInitialized) | |
| 156 this.updatePrefs_(prefs); | |
| 52 }, | 157 }, |
| 53 | 158 |
| 54 /** | 159 /** |
| 55 * Called when prefs are fetched from settingsPrivate. | 160 * Called when prefs are fetched from settingsPrivate. |
| 56 * @param {!Array<!PrefObject>} prefs | 161 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
| 57 * @private | 162 * @private |
| 58 */ | 163 */ |
| 59 onPrefsFetched_: function(prefs) { | 164 onSettingsPrivatePrefsFetched_: function(prefs) { |
| 60 this.updatePrefs_(prefs, true); | 165 this.updatePrefs_(prefs); |
| 61 | 166 |
| 62 CrSettingsPrefs.isInitialized = true; | 167 CrSettingsPrefs.isInitialized = true; |
| 63 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED)); | 168 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED)); |
| 64 }, | 169 }, |
| 65 | 170 |
| 66 | 171 /** |
| 67 /** | 172 * Checks the result of calling settingsPrivate.setPref. |
| 68 * Updates the settings model with the given prefs. | 173 * @param {string} key The key used in the call to setPref. |
| 69 * @param {!Array<!PrefObject>} prefs | 174 * @param {boolean} success True if setting the pref succeeded. |
| 70 * @param {boolean} shouldObserve Whether each of the prefs should be | 175 * @private |
| 71 * observed. | 176 */ |
| 72 * @private | 177 setPrefCallback_: function(key, success) { |
| 73 */ | 178 if (success) |
| 74 updatePrefs_: function(prefs, shouldObserve) { | 179 return; |
| 75 prefs.forEach(function(prefObj) { | 180 |
| 76 let root = this.prefStore; | 181 // Get the current pref value from chrome.settingsPrivate to ensure the |
| 77 let tokens = prefObj.key.split('.'); | 182 // UI stays up to date. |
| 78 | 183 chrome.settingsPrivate.getPref(key, function(pref) { |
| 79 assert(tokens.length > 0); | 184 this.updatePrefs_([pref]); |
| 80 | 185 }.bind(this)); |
| 81 for (let i = 0; i < tokens.length; i++) { | 186 }, |
| 82 let token = tokens[i]; | 187 |
| 83 | 188 /** |
| 84 if (!root.hasOwnProperty(token)) { | 189 * Updates the prefs model with the given prefs. |
| 85 let path = 'prefStore.' + tokens.slice(0, i + 1).join('.'); | 190 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
| 86 this.set(path, {}); | 191 * @private |
| 87 } | 192 */ |
| 88 root = root[token]; | 193 updatePrefs_: function(prefs) { |
| 89 } | 194 prefs.forEach(function(newPrefObj) { |
| 90 | 195 // Use the PrefObject from settingsPrivate to create a PrefWrapper in |
| 91 // NOTE: Do this copy rather than just a re-assignment, so that the | 196 // prefWrappers_ at the pref's key. |
| 92 // observer fires. | 197 this.prefWrappers_[newPrefObj.key] = |
| 93 for (let objKey in prefObj) { | 198 this.createPrefWrapper_(newPrefObj); |
| 94 let path = 'prefStore.' + prefObj.key + '.' + objKey; | 199 |
| 95 | 200 // Set or update the pref in |prefs|. This triggers observers in the UI, |
| 96 // Handle lists specially. We don't want to call this.set() | 201 // which update controls associated with the pref. |
| 97 // unconditionally upon updating a list value, since even its contents | 202 this.setPref_(newPrefObj); |
| 98 // are the same as the old list, doing this set() may cause an | |
| 99 // infinite update cycle (http://crbug.com/498586). | |
| 100 if (objKey == 'value' && | |
| 101 prefObj.type == chrome.settingsPrivate.PrefType.LIST && | |
| 102 !this.shouldUpdateListPrefValue_(root, prefObj['value'])) { | |
| 103 continue; | |
| 104 } | |
| 105 | |
| 106 this.set(path, prefObj[objKey]); | |
| 107 } | |
| 108 | |
| 109 if (shouldObserve) { | |
| 110 Object.observe(root, this.propertyChangeCallback_, ['update']); | |
| 111 } | |
| 112 }, this); | 203 }, this); |
| 113 }, | 204 }, |
| 114 | 205 |
| 115 | 206 /** |
| 116 /** | 207 * Given a 'property-changed' path, returns the key of the preference the |
| 117 * @param {Object} root The root object for a pref that contains a list | 208 * path refers to. E.g., if the path of the changed property is |
| 209 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is | |
| 210 * 'search.suggest_enabled'. | |
| 211 * @param {string} path | |
| 212 * @return {string} | |
| 213 * @private | |
| 214 */ | |
| 215 getPrefKeyFromPath_: function(path) { | |
| 216 // Skip the first token, which refers to the member variable (this.prefs). | |
| 217 var parts = path.split('.'); | |
| 218 assert(parts.shift() == 'prefs'); | |
| 219 | |
| 220 for (let i = 1; i <= parts.length; i++) { | |
| 221 let key = parts.slice(0, i).join('.'); | |
| 222 // The prefWrappers_ keys match the pref keys. | |
| 223 if (this.prefWrappers_[key] != undefined) | |
| 224 return key; | |
| 225 } | |
| 226 return ''; | |
| 227 }, | |
| 228 | |
| 229 /** | |
| 230 * Sets or updates the pref denoted by newPrefObj.key in the publicy exposed | |
| 231 * |prefs| property. | |
| 232 * @param {chrome.settingsPrivate.PrefObject} newPrefObj The pref object to | |
| 233 * update the pref with. | |
| 234 * @private | |
| 235 */ | |
| 236 setPref_: function(newPrefObj) { | |
| 237 // Check if the pref exists already in the Polymer |prefs| object. | |
| 238 if (this.get(newPrefObj.key, this.prefs)) { | |
| 239 // Update just the value, notifying listeners of the change. | |
| 240 this.set('prefs.' + newPrefObj.key + '.value', newPrefObj.value); | |
| 241 } else { | |
| 242 // Add the pref to |prefs|. cr.exportPath doesn't use Polymer.Base.set, | |
| 243 // which is OK because the nested property update events aren't useful. | |
| 244 cr.exportPath(newPrefObj.key, newPrefObj, this.prefs); | |
|
michaelpg
2015/08/28 20:43:05
"ERROR - cr.exportPath() should have exactly 1 arg
Dan Beam
2015/08/28 20:50:05
this is wrong:
https://code.google.com/p/chromium/
michaelpg
2015/08/28 20:58:10
should we remove that check? or not use exportPath
Dan Beam
2015/08/28 21:06:56
we should fix it
michaelpg
2015/08/29 01:26:00
Done.
| |
| 245 // Notify listeners of the change at the preference key. | |
| 246 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); | |
| 247 } | |
| 248 }, | |
| 249 | |
| 250 /** | |
| 251 * Creates a PrefWrapper object from a chrome.settingsPrivate pref. | |
| 252 * @param {!chrome.settingsPrivate.PrefObject} prefObj | |
| 253 * PrefObject received from chrome.settingsPrivate. | |
| 254 * @return {PrefWrapper} An object containing a copy of the PrefObject's | |
| 118 * value. | 255 * value. |
| 119 * @param {!Array} newValue The new list value. | 256 * @private |
| 120 * @return {boolean} Whether the new value is different from the one in | 257 */ |
| 121 * root, thus necessitating a pref update. | 258 createPrefWrapper_: function(prefObj) { |
| 122 */ | 259 return prefObj.type == chrome.settingsPrivate.PrefType.LIST ? |
| 123 shouldUpdateListPrefValue_: function(root, newValue) { | 260 new ListPrefWrapper(prefObj) : new PrefWrapper(prefObj); |
| 124 if (root.value == null || | |
| 125 root.value.length != newValue.length) { | |
| 126 return true; | |
| 127 } | |
| 128 | |
| 129 for (let i = 0; i < newValue.length; i++) { | |
| 130 if (root.value != null && root.value[i] != newValue[i]) { | |
| 131 return true; | |
| 132 } | |
| 133 } | |
| 134 | |
| 135 return false; | |
| 136 }, | |
| 137 | |
| 138 /** | |
| 139 * Called when a property of a pref changes. | |
| 140 * @param {!Array<!Object>} changes An array of objects describing changes. | |
| 141 * @see http://www.html5rocks.com/en/tutorials/es7/observe/ | |
| 142 * @private | |
| 143 */ | |
| 144 propertyChangeCallback_: function(changes) { | |
| 145 changes.forEach(function(change) { | |
| 146 // UI should only be able to change the value of a setting for now, not | |
| 147 // disabled, etc. | |
| 148 assert(change.name == 'value'); | |
| 149 | |
| 150 let newValue = change.object[change.name]; | |
| 151 assert(newValue !== undefined); | |
| 152 | |
| 153 chrome.settingsPrivate.setPref( | |
| 154 change.object['key'], | |
| 155 newValue, | |
| 156 /* pageId */ '', | |
| 157 /* callback */ function() {}); | |
| 158 }); | |
| 159 }, | 261 }, |
| 160 }); | 262 }); |
| 161 })(); | 263 })(); |
| OLD | NEW |