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' models Chrome settings and preferences, listening for | 7 * 'cr-settings-prefs' exposes a sinlgeton model of Chrome settings and |
| 8 * changes to Chrome prefs whitelisted in chrome.settingsPrivate. | 8 * preferences, which listens to changes to Chrome prefs whitelisted in |
| 9 * When changing prefs in this element's 'prefs' property via the UI, this | 9 * chrome.settingsPrivate. When changing prefs in this element's 'prefs' |
| 10 * element tries to set those preferences in Chrome. Whether or not the calls to | 10 * property via the UI, the model tries to set those preferences in Chrome. |
| 11 * settingsPrivate.setPref succeed, 'prefs' is eventually consistent with the | 11 * Whether or not the calls to settingsPrivate.setPref succeed, 'prefs' |
| 12 * Chrome pref store. | 12 * is eventually consistent with the 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 |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 129 properties: { | 129 properties: { |
| 130 /** | 130 /** |
| 131 * Object containing all preferences, for use by Polymer controls. | 131 * Object containing all preferences, for use by Polymer controls. |
| 132 */ | 132 */ |
| 133 prefs: { | 133 prefs: { |
| 134 type: Object, | 134 type: Object, |
| 135 notify: true, | 135 notify: true, |
| 136 }, | 136 }, |
| 137 | 137 |
| 138 /** | 138 /** |
| 139 * Shared private state. | |
| 140 * @type {!Element} | |
| 141 */ | |
| 142 prefsPrivate_: { | |
| 143 type: Object, | |
| 144 value: document.createElement('cr-settings-prefs-private'), | |
| 145 }, | |
| 146 }, | |
| 147 | |
| 148 observers: [ | |
| 149 'prefsChanged_(prefs.*)', | |
| 150 ], | |
| 151 | |
| 152 /** @override */ | |
| 153 ready: function() { | |
| 154 this.startListening_(); | |
| 155 this.prefsPrivate_.initialize(); | |
|
Dan Beam
2015/09/21 22:25:20
nit: reverse these if possible
michaelpg
2015/09/22 00:34:07
Done.
| |
| 156 }, | |
| 157 | |
| 158 /** | |
| 159 * Binds this.prefs to the cr-settings-prefs-private's shared prefs once | |
| 160 * preferences are initialized. | |
| 161 * @private | |
| 162 */ | |
| 163 startListening_: function() { | |
| 164 CrSettingsPrefs.initialized.then( | |
| 165 // Squelch to prevent prefsChanged_ from notifying prefsPrivate_. | |
| 166 this.squelching_.bind(this, function() { | |
| 167 this.prefs = this.prefsPrivate_.prefs; | |
| 168 // Adding multiple listeners would create excessive duplicate work. | |
| 169 assert(!this.listening_); | |
|
Dan Beam
2015/09/21 22:25:20
there seems to be a graceful fix for this: just un
michaelpg
2015/09/22 00:34:07
Done.
| |
| 170 this.listening_ = true; | |
| 171 this.listen( | |
| 172 this.prefsPrivate_, 'prefs-changed', 'prefsPrivateChanged_'); | |
| 173 })); | |
|
Dan Beam
2015/09/21 22:25:20
CrSettingsPrefs.initialized.then(function() {
th
michaelpg
2015/09/22 00:34:06
Done.
| |
| 174 }, | |
| 175 | |
| 176 /** | |
| 177 * Stops listening for changes to cr-settings-prefs-private's shared prefs. | |
| 178 * @private | |
| 179 */ | |
| 180 stopListening_: function() { | |
| 181 if (this.listening_) { | |
|
Dan Beam
2015/09/21 22:25:20
is this if really necessary?
michaelpg
2015/09/22 00:34:07
Done.
| |
| 182 this.unlisten( | |
| 183 this.prefsPrivate_, 'prefs-changed', 'prefsPrivateChanged_'); | |
| 184 this.listening_ = false; | |
| 185 } | |
| 186 }, | |
| 187 | |
| 188 /** | |
| 189 * Handles changes reported by prefsPrivate_. | |
| 190 * @private | |
| 191 */ | |
| 192 prefsPrivateChanged_: function(e) { | |
| 193 // Squelch because we've defeated Polymer's internal dirty-checking. | |
| 194 this.squelching_(function() { | |
| 195 // Forward notification to host. | |
| 196 this.fire(e.type, e.detail, {bubbles: false}); | |
| 197 }); | |
| 198 }, | |
| 199 | |
| 200 /** | |
| 201 * Forwards changes to this.prefs to cr-settings-prefs-private. | |
| 202 * @private | |
| 203 */ | |
| 204 prefsChanged_: function(info) { | |
| 205 // Squelch to avoid informing prefsPrivate_ of changes that came from it | |
| 206 // and re-processing changes made in other instances of this element. | |
| 207 if (!this.squelch_) | |
| 208 this.prefsPrivate_.fire('prefs-changed', info, {bubbles: false}); | |
| 209 }, | |
| 210 | |
| 211 /** | |
| 212 * Sets a "squelch" switch before calling the function, so functions can | |
| 213 * return early when the switch is active. | |
| 214 * @param {!function()} fn | |
| 215 * @private | |
| 216 */ | |
| 217 squelching_: function(fn) { | |
|
Dan Beam
2015/09/21 22:25:20
runWhileIgnoringChanges_ IMO
michaelpg
2015/09/22 00:34:06
Done.
| |
| 218 this.squelch_ = true; | |
|
Dan Beam
2015/09/21 22:25:20
this.ignoreChanges_ or this.ignorePrefChanges_
michaelpg
2015/09/22 00:34:07
Done.
| |
| 219 fn.call(this); | |
| 220 // We can unset squelch_ now because change notifications are synchronous. | |
| 221 this.squelch_ = false; | |
|
Dan Beam
2015/09/21 22:25:20
instead of setting a flag, just stop listening dur
michaelpg
2015/09/22 00:34:07
I'm not sure how that would work... the interactio
Dan Beam
2015/09/22 06:12:31
any time I see "listen to events" followed by "but
| |
| 222 }, | |
| 223 | |
| 224 /** | |
| 225 * Uninitializes this element to remove it from tests. Also resets | |
| 226 * cr-settings-prefs-private, allowing newly created elements to | |
| 227 * re-initialize it. | |
| 228 */ | |
| 229 resetForTesting: function() { | |
| 230 this.stopListening_(); | |
| 231 this.prefsPrivate_.resetForTesting(); | |
| 232 }, | |
| 233 }); | |
| 234 | |
| 235 /** | |
| 236 * Privately used element that contains, listens to and updates the shared | |
| 237 * prefs state. | |
| 238 */ | |
| 239 Polymer({ | |
| 240 is: 'cr-settings-prefs-private', | |
| 241 | |
| 242 properties: { | |
| 243 /** | |
| 244 * Object containing all preferences, for use by Polymer controls. | |
| 245 * @type {(Object|undefined)} | |
| 246 */ | |
| 247 prefs: { | |
| 248 type: Object, | |
| 249 notify: true, | |
| 250 }, | |
| 251 | |
| 252 /** | |
| 139 * Map of pref keys to values representing the state of the Chrome | 253 * Map of pref keys to values representing the state of the Chrome |
| 140 * pref store as of the last update from the API. | 254 * pref store as of the last update from the API. |
| 141 * @type {Object<*>} | 255 * @type {Object<*>} |
| 142 * @private | 256 * @private |
| 143 */ | 257 */ |
| 144 lastPrefValues_: { | 258 lastPrefValues_: { |
| 145 type: Object, | 259 type: Object, |
| 146 value: function() { return {}; }, | 260 value: function() { return {}; }, |
| 147 }, | 261 }, |
| 148 }, | 262 }, |
| 149 | 263 |
| 150 observers: [ | 264 // Listen for the manually fired prefs-changed event. |
| 151 'prefsChanged_(prefs.*)', | 265 listeners: { |
| 152 ], | 266 'prefs-changed': 'prefsChanged_', |
| 267 }, | |
| 153 | 268 |
| 154 settingsApi_: chrome.settingsPrivate, | 269 settingsApi_: chrome.settingsPrivate, |
| 155 | 270 |
| 156 /** @override */ | 271 initialize: function() { |
| 157 ready: function() { | 272 // Only initialize once (or after resetForTesting() is called). |
| 273 if (this.initialized_) | |
| 274 return; | |
| 275 this.initialized_ = true; | |
| 276 | |
| 158 // Set window.mockApi to pass a custom settings API, i.e. for tests. | 277 // Set window.mockApi to pass a custom settings API, i.e. for tests. |
| 159 // TODO(michaelpg): don't use a global. | 278 // TODO(michaelpg): don't use a global. |
| 160 if (window.mockApi) | 279 if (window.mockApi) |
| 161 this.settingsApi_ = window.mockApi; | 280 this.settingsApi_ = window.mockApi; |
| 162 CrSettingsPrefs.isInitialized = false; | |
| 163 | 281 |
| 164 this.settingsApi_.onPrefsChanged.addListener( | 282 this.settingsApi_.onPrefsChanged.addListener( |
| 165 this.onSettingsPrivatePrefsChanged_.bind(this)); | 283 this.onSettingsPrivatePrefsChanged_.bind(this)); |
| 166 this.settingsApi_.getAllPrefs( | 284 this.settingsApi_.getAllPrefs( |
| 167 this.onSettingsPrivatePrefsFetched_.bind(this)); | 285 this.onSettingsPrivatePrefsFetched_.bind(this)); |
| 168 }, | 286 }, |
| 169 | 287 |
| 170 /** | 288 /** |
| 171 * Polymer callback for changes to this.prefs. | 289 * Polymer callback for changes to this.prefs. |
| 172 * @param {!{path: string, value: *}} change | 290 * @param {!CustomEvent} e |
| 291 * @param {!{path: string}} change | |
| 173 * @private | 292 * @private |
| 174 */ | 293 */ |
| 175 prefsChanged_: function(change) { | 294 prefsChanged_: function(e, change) { |
| 176 if (!CrSettingsPrefs.isInitialized) | 295 if (!CrSettingsPrefs.isInitialized) |
| 177 return; | 296 return; |
| 178 | 297 |
| 179 var key = this.getPrefKeyFromPath_(change.path); | 298 var key = this.getPrefKeyFromPath_(change.path); |
| 180 var prefStoreValue = this.lastPrefValues_[key]; | 299 var prefStoreValue = this.lastPrefValues_[key]; |
| 181 | 300 |
| 182 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | 301 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( |
| 183 this.get(key, this.prefs)); | 302 this.get(key, this.prefs)); |
| 184 | 303 |
| 185 // If settingsPrivate already has this value, do nothing. (Otherwise, | 304 // If settingsPrivate already has this value, do nothing. (Otherwise, |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 206 this.updatePrefs_(prefs); | 325 this.updatePrefs_(prefs); |
| 207 }, | 326 }, |
| 208 | 327 |
| 209 /** | 328 /** |
| 210 * Called when prefs are fetched from settingsPrivate. | 329 * Called when prefs are fetched from settingsPrivate. |
| 211 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs | 330 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
| 212 * @private | 331 * @private |
| 213 */ | 332 */ |
| 214 onSettingsPrivatePrefsFetched_: function(prefs) { | 333 onSettingsPrivatePrefsFetched_: function(prefs) { |
| 215 this.updatePrefs_(prefs); | 334 this.updatePrefs_(prefs); |
| 216 | 335 // Prefs are now initialized. |
| 217 CrSettingsPrefs.isInitialized = true; | 336 CrSettingsPrefs.setInitialized(); |
| 218 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED)); | |
| 219 }, | 337 }, |
| 220 | 338 |
| 221 /** | 339 /** |
| 222 * Checks the result of calling settingsPrivate.setPref. | 340 * Checks the result of calling settingsPrivate.setPref. |
| 223 * @param {string} key The key used in the call to setPref. | 341 * @param {string} key The key used in the call to setPref. |
| 224 * @param {boolean} success True if setting the pref succeeded. | 342 * @param {boolean} success True if setting the pref succeeded. |
| 225 * @private | 343 * @private |
| 226 */ | 344 */ |
| 227 setPrefCallback_: function(key, success) { | 345 setPrefCallback_: function(key, success) { |
| 228 if (success) | 346 if (success) |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 241 * @private | 359 * @private |
| 242 */ | 360 */ |
| 243 updatePrefs_: function(newPrefs) { | 361 updatePrefs_: function(newPrefs) { |
| 244 // Use the existing prefs object or create it. | 362 // Use the existing prefs object or create it. |
| 245 var prefs = this.prefs || {}; | 363 var prefs = this.prefs || {}; |
| 246 newPrefs.forEach(function(newPrefObj) { | 364 newPrefs.forEach(function(newPrefObj) { |
| 247 // Use the PrefObject from settingsPrivate to create a copy in | 365 // Use the PrefObject from settingsPrivate to create a copy in |
| 248 // lastPrefValues_ at the pref's key. | 366 // lastPrefValues_ at the pref's key. |
| 249 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); | 367 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); |
| 250 | 368 |
| 251 // Add the pref to |prefs|. | 369 if (!deepEqual(this.get(newPrefObj.key, prefs), newPrefObj)) { |
| 252 cr.exportPath(newPrefObj.key, newPrefObj, prefs); | 370 // Add the pref to |prefs|. |
| 253 // If this.prefs already exists, notify listeners of the change. | 371 cr.exportPath(newPrefObj.key, newPrefObj, prefs); |
| 254 if (prefs == this.prefs) | 372 // If this.prefs already exists, notify listeners of the change. |
| 255 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); | 373 if (prefs == this.prefs) |
| 374 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); | |
| 375 } | |
| 256 }, this); | 376 }, this); |
| 257 if (!this.prefs) | 377 if (!this.prefs) |
| 258 this.prefs = prefs; | 378 this.prefs = prefs; |
| 259 }, | 379 }, |
| 260 | 380 |
| 261 /** | 381 /** |
| 262 * Given a 'property-changed' path, returns the key of the preference the | 382 * Given a 'property-changed' path, returns the key of the preference the |
| 263 * path refers to. E.g., if the path of the changed property is | 383 * path refers to. E.g., if the path of the changed property is |
| 264 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is | 384 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is |
| 265 * 'search.suggest_enabled'. | 385 * 'search.suggest_enabled'. |
| 266 * @param {string} path | 386 * @param {string} path |
| 267 * @return {string} | 387 * @return {string} |
| 268 * @private | 388 * @private |
| 269 */ | 389 */ |
| 270 getPrefKeyFromPath_: function(path) { | 390 getPrefKeyFromPath_: function(path) { |
| 271 // Skip the first token, which refers to the member variable (this.prefs). | 391 // Skip the first token, which refers to the member variable (this.prefs). |
| 272 var parts = path.split('.'); | 392 var parts = path.split('.'); |
| 273 assert(parts.shift() == 'prefs'); | 393 assert(parts.shift() == 'prefs'); |
| 274 | 394 |
| 275 for (let i = 1; i <= parts.length; i++) { | 395 for (let i = 1; i <= parts.length; i++) { |
| 276 let key = parts.slice(0, i).join('.'); | 396 let key = parts.slice(0, i).join('.'); |
| 277 // The lastPrefValues_ keys match the pref keys. | 397 // The lastPrefValues_ keys match the pref keys. |
| 278 if (this.lastPrefValues_.hasOwnProperty(key)) | 398 if (this.lastPrefValues_.hasOwnProperty(key)) |
| 279 return key; | 399 return key; |
| 280 } | 400 } |
| 281 return ''; | 401 return ''; |
| 282 }, | 402 }, |
| 403 | |
| 404 /** | |
| 405 * Resets the element so it can be re-initialized with a new prefs state. | |
| 406 */ | |
| 407 resetForTesting: function() { | |
| 408 this.prefs = undefined; | |
| 409 this.lastPrefValues_ = {}; | |
| 410 this.initialized_ = false; | |
| 411 }, | |
| 283 }); | 412 }); |
| 284 })(); | 413 })(); |
| OLD | NEW |