| 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 singleton 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 singleton model tries to set those preferences in |
| 11 * settingsPrivate.setPref succeed, 'prefs' is eventually consistent with the | 11 * Chrome. 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-checkbox pref="{{prefs.homepage_is_newtabpage}}"> |
| 18 * </cr-settings-checkbox> |
| 18 * | 19 * |
| 19 * @group Chrome Settings Elements | 20 * @group Chrome Settings Elements |
| 20 * @element cr-settings-prefs | 21 * @element cr-settings-prefs |
| 21 */ | 22 */ |
| 22 | 23 |
| 23 (function() { | 24 (function() { |
| 24 'use strict'; | 25 'use strict'; |
| 25 | 26 |
| 26 /** | 27 /** |
| 27 * Checks whether two values are recursively equal. Only compares serializable | 28 * Checks whether two values are recursively equal. Only compares serializable |
| (...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 129 properties: { | 130 properties: { |
| 130 /** | 131 /** |
| 131 * Object containing all preferences, for use by Polymer controls. | 132 * Object containing all preferences, for use by Polymer controls. |
| 132 */ | 133 */ |
| 133 prefs: { | 134 prefs: { |
| 134 type: Object, | 135 type: Object, |
| 135 notify: true, | 136 notify: true, |
| 136 }, | 137 }, |
| 137 | 138 |
| 138 /** | 139 /** |
| 140 * Singleton element created at startup which provides the prefs model. |
| 141 * @type {!Element} |
| 142 */ |
| 143 singleton_: { |
| 144 type: Object, |
| 145 value: document.createElement('cr-settings-prefs-singleton'), |
| 146 }, |
| 147 }, |
| 148 |
| 149 observers: [ |
| 150 'prefsChanged_(prefs.*)', |
| 151 ], |
| 152 |
| 153 /** @override */ |
| 154 ready: function() { |
| 155 this.singleton_.initialize(); |
| 156 this.startListening_(); |
| 157 }, |
| 158 |
| 159 /** |
| 160 * Binds this.prefs to the cr-settings-prefs-singleton's shared prefs once |
| 161 * preferences are initialized. |
| 162 * @private |
| 163 */ |
| 164 startListening_: function() { |
| 165 CrSettingsPrefs.initialized.then(function() { |
| 166 // Ignore changes to prevent prefsChanged_ from notifying singleton_. |
| 167 this.runWhileIgnoringChanges_(function() { |
| 168 this.prefs = this.singleton_.prefs; |
| 169 this.stopListening_(); |
| 170 this.listen( |
| 171 this.singleton_, 'prefs-changed', 'singletonPrefsChanged_'); |
| 172 }); |
| 173 }.bind(this)); |
| 174 }, |
| 175 |
| 176 /** |
| 177 * Stops listening for changes to cr-settings-prefs-singleton's shared |
| 178 * prefs. |
| 179 * @private |
| 180 */ |
| 181 stopListening_: function() { |
| 182 this.unlisten( |
| 183 this.singleton_, 'prefs-changed', 'singletonPrefsChanged_'); |
| 184 }, |
| 185 |
| 186 /** |
| 187 * Handles changes reported by singleton_ by forwarding them to the host. |
| 188 * @private |
| 189 */ |
| 190 singletonPrefsChanged_: function(e) { |
| 191 // Ignore changes because we've defeated Polymer's dirty-checking. |
| 192 this.runWhileIgnoringChanges_(function() { |
| 193 // Forward notification to host. |
| 194 this.fire(e.type, e.detail, {bubbles: false}); |
| 195 }); |
| 196 }, |
| 197 |
| 198 /** |
| 199 * Forwards changes to this.prefs to cr-settings-prefs-singleton. |
| 200 * @private |
| 201 */ |
| 202 prefsChanged_: function(info) { |
| 203 // Ignore changes that came from singleton_ so we don't re-process |
| 204 // changes made in other instances of this element. |
| 205 if (!this.ignoreChanges_) |
| 206 this.singleton_.fire('prefs-changed', info, {bubbles: false}); |
| 207 }, |
| 208 |
| 209 /** |
| 210 * Sets ignoreChanged_ before calling the function to suppress change |
| 211 * events that are manually handled. |
| 212 * @param {!function()} fn |
| 213 * @private |
| 214 */ |
| 215 runWhileIgnoringChanges_: function(fn) { |
| 216 assert(!this.ignoreChanges_, |
| 217 'Nested calls to runWhileIgnoringChanges_ are not supported'); |
| 218 this.ignoreChanges_ = true; |
| 219 fn.call(this); |
| 220 // We can unset ignoreChanges_ now because change notifications |
| 221 // are synchronous. |
| 222 this.ignoreChanges_ = false; |
| 223 }, |
| 224 |
| 225 /** |
| 226 * Uninitializes this element to remove it from tests. Also resets |
| 227 * cr-settings-prefs-singleton, allowing newly created elements to |
| 228 * re-initialize it. |
| 229 */ |
| 230 resetForTesting: function() { |
| 231 this.stopListening_(); |
| 232 this.singleton_.resetForTesting(); |
| 233 }, |
| 234 }); |
| 235 |
| 236 /** |
| 237 * Privately used element that contains, listens to and updates the shared |
| 238 * prefs state. |
| 239 */ |
| 240 Polymer({ |
| 241 is: 'cr-settings-prefs-singleton', |
| 242 |
| 243 properties: { |
| 244 /** |
| 245 * Object containing all preferences, for use by Polymer controls. |
| 246 * @type {Object|undefined} |
| 247 */ |
| 248 prefs: { |
| 249 type: Object, |
| 250 notify: true, |
| 251 }, |
| 252 |
| 253 /** |
| 139 * Map of pref keys to values representing the state of the Chrome | 254 * Map of pref keys to values representing the state of the Chrome |
| 140 * pref store as of the last update from the API. | 255 * pref store as of the last update from the API. |
| 141 * @type {Object<*>} | 256 * @type {Object<*>} |
| 142 * @private | 257 * @private |
| 143 */ | 258 */ |
| 144 lastPrefValues_: { | 259 lastPrefValues_: { |
| 145 type: Object, | 260 type: Object, |
| 146 value: function() { return {}; }, | 261 value: function() { return {}; }, |
| 147 }, | 262 }, |
| 148 }, | 263 }, |
| 149 | 264 |
| 150 observers: [ | 265 // Listen for the manually fired prefs-changed event. |
| 151 'prefsChanged_(prefs.*)', | 266 listeners: { |
| 152 ], | 267 'prefs-changed': 'prefsChanged_', |
| 268 }, |
| 153 | 269 |
| 154 settingsApi_: chrome.settingsPrivate, | 270 settingsApi_: chrome.settingsPrivate, |
| 155 | 271 |
| 156 /** @override */ | 272 initialize: function() { |
| 157 ready: function() { | 273 // Only initialize once (or after resetForTesting() is called). |
| 274 if (this.initialized_) |
| 275 return; |
| 276 this.initialized_ = true; |
| 277 |
| 158 // Set window.mockApi to pass a custom settings API, i.e. for tests. | 278 // Set window.mockApi to pass a custom settings API, i.e. for tests. |
| 159 // TODO(michaelpg): don't use a global. | 279 // TODO(michaelpg): don't use a global. |
| 160 if (window.mockApi) | 280 if (window.mockApi) |
| 161 this.settingsApi_ = window.mockApi; | 281 this.settingsApi_ = window.mockApi; |
| 162 CrSettingsPrefs.isInitialized = false; | |
| 163 | 282 |
| 164 this.settingsApi_.onPrefsChanged.addListener( | 283 this.settingsApi_.onPrefsChanged.addListener( |
| 165 this.onSettingsPrivatePrefsChanged_.bind(this)); | 284 this.onSettingsPrivatePrefsChanged_.bind(this)); |
| 166 this.settingsApi_.getAllPrefs( | 285 this.settingsApi_.getAllPrefs( |
| 167 this.onSettingsPrivatePrefsFetched_.bind(this)); | 286 this.onSettingsPrivatePrefsFetched_.bind(this)); |
| 168 }, | 287 }, |
| 169 | 288 |
| 170 /** | 289 /** |
| 171 * Polymer callback for changes to this.prefs. | 290 * Polymer callback for changes to this.prefs. |
| 172 * @param {!{path: string, value: *}} change | 291 * @param {!CustomEvent} e |
| 292 * @param {!{path: string}} change |
| 173 * @private | 293 * @private |
| 174 */ | 294 */ |
| 175 prefsChanged_: function(change) { | 295 prefsChanged_: function(e, change) { |
| 176 if (!CrSettingsPrefs.isInitialized) | 296 if (!CrSettingsPrefs.isInitialized) |
| 177 return; | 297 return; |
| 178 | 298 |
| 179 var key = this.getPrefKeyFromPath_(change.path); | 299 var key = this.getPrefKeyFromPath_(change.path); |
| 180 var prefStoreValue = this.lastPrefValues_[key]; | 300 var prefStoreValue = this.lastPrefValues_[key]; |
| 181 | 301 |
| 182 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | 302 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( |
| 183 this.get(key, this.prefs)); | 303 this.get(key, this.prefs)); |
| 184 | 304 |
| 185 // If settingsPrivate already has this value, do nothing. (Otherwise, | 305 // If settingsPrivate already has this value, do nothing. (Otherwise, |
| (...skipping 20 matching lines...) Expand all Loading... |
| 206 this.updatePrefs_(prefs); | 326 this.updatePrefs_(prefs); |
| 207 }, | 327 }, |
| 208 | 328 |
| 209 /** | 329 /** |
| 210 * Called when prefs are fetched from settingsPrivate. | 330 * Called when prefs are fetched from settingsPrivate. |
| 211 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs | 331 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
| 212 * @private | 332 * @private |
| 213 */ | 333 */ |
| 214 onSettingsPrivatePrefsFetched_: function(prefs) { | 334 onSettingsPrivatePrefsFetched_: function(prefs) { |
| 215 this.updatePrefs_(prefs); | 335 this.updatePrefs_(prefs); |
| 216 | 336 CrSettingsPrefs.setInitialized(); |
| 217 CrSettingsPrefs.isInitialized = true; | |
| 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 |