| 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(); |
| 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_); |
| 170 this.listening_ = true; |
| 171 this.listen( |
| 172 this.prefsPrivate_, 'prefs-changed', 'prefsPrivateChanged_'); |
| 173 })); |
| 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_) { |
| 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) { |
| 218 this.squelch_ = true; |
| 219 fn.call(this); |
| 220 // We can unset squelch_ now because change notifications are synchronous. |
| 221 this.squelch_ = false; |
| 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 */ |
| 246 prefs: { |
| 247 type: Object, |
| 248 notify: true, |
| 249 }, |
| 250 |
| 251 /** |
| 139 * Map of pref keys to values representing the state of the Chrome | 252 * Map of pref keys to values representing the state of the Chrome |
| 140 * pref store as of the last update from the API. | 253 * pref store as of the last update from the API. |
| 141 * @type {Object<*>} | 254 * @type {Object<*>} |
| 142 * @private | 255 * @private |
| 143 */ | 256 */ |
| 144 lastPrefValues_: { | 257 lastPrefValues_: { |
| 145 type: Object, | 258 type: Object, |
| 146 value: function() { return {}; }, | 259 value: function() { return {}; }, |
| 147 }, | 260 }, |
| 148 }, | 261 }, |
| 149 | 262 |
| 150 observers: [ | 263 // Listen for the manually fired prefs-changed event. |
| 151 'prefsChanged_(prefs.*)', | 264 listeners: { |
| 152 ], | 265 'prefs-changed': 'prefsChanged_', |
| 266 }, |
| 153 | 267 |
| 154 settingsApi_: chrome.settingsPrivate, | 268 settingsApi_: chrome.settingsPrivate, |
| 155 | 269 |
| 156 /** @override */ | 270 initialize: function() { |
| 157 ready: function() { | 271 // Only initialize once (or after resetForTesting() is called). |
| 272 if (this.initialized_) |
| 273 return; |
| 274 this.initialized_ = true; |
| 275 |
| 158 // Set window.mockApi to pass a custom settings API, i.e. for tests. | 276 // Set window.mockApi to pass a custom settings API, i.e. for tests. |
| 159 // TODO(michaelpg): don't use a global. | 277 // TODO(michaelpg): don't use a global. |
| 160 if (window.mockApi) | 278 if (window.mockApi) |
| 161 this.settingsApi_ = window.mockApi; | 279 this.settingsApi_ = window.mockApi; |
| 162 CrSettingsPrefs.isInitialized = false; | |
| 163 | 280 |
| 164 this.settingsApi_.onPrefsChanged.addListener( | 281 this.settingsApi_.onPrefsChanged.addListener( |
| 165 this.onSettingsPrivatePrefsChanged_.bind(this)); | 282 this.onSettingsPrivatePrefsChanged_.bind(this)); |
| 166 this.settingsApi_.getAllPrefs( | 283 this.settingsApi_.getAllPrefs( |
| 167 this.onSettingsPrivatePrefsFetched_.bind(this)); | 284 this.onSettingsPrivatePrefsFetched_.bind(this)); |
| 168 }, | 285 }, |
| 169 | 286 |
| 170 /** | 287 /** |
| 171 * Polymer callback for changes to this.prefs. | 288 * Polymer callback for changes to this.prefs. |
| 172 * @param {!{path: string, value: *}} change | 289 * @param {!CustomEvent} e |
| 290 * @param {!{path: string}} change |
| 173 * @private | 291 * @private |
| 174 */ | 292 */ |
| 175 prefsChanged_: function(change) { | 293 prefsChanged_: function(e, change) { |
| 176 if (!CrSettingsPrefs.isInitialized) | 294 if (!CrSettingsPrefs.isInitialized) |
| 177 return; | 295 return; |
| 178 | 296 |
| 179 var key = this.getPrefKeyFromPath_(change.path); | 297 var key = this.getPrefKeyFromPath_(change.path); |
| 180 var prefStoreValue = this.lastPrefValues_[key]; | 298 var prefStoreValue = this.lastPrefValues_[key]; |
| 181 | 299 |
| 182 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | 300 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( |
| 183 this.get(key, this.prefs)); | 301 this.get(key, this.prefs)); |
| 184 | 302 |
| 185 // If settingsPrivate already has this value, do nothing. (Otherwise, | 303 // If settingsPrivate already has this value, do nothing. (Otherwise, |
| (...skipping 20 matching lines...) Expand all Loading... |
| 206 this.updatePrefs_(prefs); | 324 this.updatePrefs_(prefs); |
| 207 }, | 325 }, |
| 208 | 326 |
| 209 /** | 327 /** |
| 210 * Called when prefs are fetched from settingsPrivate. | 328 * Called when prefs are fetched from settingsPrivate. |
| 211 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs | 329 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
| 212 * @private | 330 * @private |
| 213 */ | 331 */ |
| 214 onSettingsPrivatePrefsFetched_: function(prefs) { | 332 onSettingsPrivatePrefsFetched_: function(prefs) { |
| 215 this.updatePrefs_(prefs); | 333 this.updatePrefs_(prefs); |
| 216 | 334 // Prefs are now initialized. |
| 217 CrSettingsPrefs.isInitialized = true; | 335 CrSettingsPrefs.setInitialized(); |
| 218 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED)); | |
| 219 }, | 336 }, |
| 220 | 337 |
| 221 /** | 338 /** |
| 222 * Checks the result of calling settingsPrivate.setPref. | 339 * Checks the result of calling settingsPrivate.setPref. |
| 223 * @param {string} key The key used in the call to setPref. | 340 * @param {string} key The key used in the call to setPref. |
| 224 * @param {boolean} success True if setting the pref succeeded. | 341 * @param {boolean} success True if setting the pref succeeded. |
| 225 * @private | 342 * @private |
| 226 */ | 343 */ |
| 227 setPrefCallback_: function(key, success) { | 344 setPrefCallback_: function(key, success) { |
| 228 if (success) | 345 if (success) |
| (...skipping 12 matching lines...) Expand all Loading... |
| 241 * @private | 358 * @private |
| 242 */ | 359 */ |
| 243 updatePrefs_: function(newPrefs) { | 360 updatePrefs_: function(newPrefs) { |
| 244 // Use the existing prefs object or create it. | 361 // Use the existing prefs object or create it. |
| 245 var prefs = this.prefs || {}; | 362 var prefs = this.prefs || {}; |
| 246 newPrefs.forEach(function(newPrefObj) { | 363 newPrefs.forEach(function(newPrefObj) { |
| 247 // Use the PrefObject from settingsPrivate to create a copy in | 364 // Use the PrefObject from settingsPrivate to create a copy in |
| 248 // lastPrefValues_ at the pref's key. | 365 // lastPrefValues_ at the pref's key. |
| 249 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); | 366 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value); |
| 250 | 367 |
| 251 // Add the pref to |prefs|. | 368 if (!deepEqual(this.get(newPrefObj.key, prefs)), newPrefObj) { |
| 252 cr.exportPath(newPrefObj.key, newPrefObj, prefs); | 369 // Add the pref to |prefs|. |
| 253 // If this.prefs already exists, notify listeners of the change. | 370 cr.exportPath(newPrefObj.key, newPrefObj, prefs); |
| 254 if (prefs == this.prefs) | 371 // If this.prefs already exists, notify listeners of the change. |
| 255 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); | 372 if (prefs == this.prefs) |
| 373 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj); |
| 374 } |
| 256 }, this); | 375 }, this); |
| 257 if (!this.prefs) | 376 if (!this.prefs) |
| 258 this.prefs = prefs; | 377 this.prefs = prefs; |
| 259 }, | 378 }, |
| 260 | 379 |
| 261 /** | 380 /** |
| 262 * Given a 'property-changed' path, returns the key of the preference the | 381 * 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 | 382 * 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 | 383 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is |
| 265 * 'search.suggest_enabled'. | 384 * 'search.suggest_enabled'. |
| 266 * @param {string} path | 385 * @param {string} path |
| 267 * @return {string} | 386 * @return {string} |
| 268 * @private | 387 * @private |
| 269 */ | 388 */ |
| 270 getPrefKeyFromPath_: function(path) { | 389 getPrefKeyFromPath_: function(path) { |
| 271 // Skip the first token, which refers to the member variable (this.prefs). | 390 // Skip the first token, which refers to the member variable (this.prefs). |
| 272 var parts = path.split('.'); | 391 var parts = path.split('.'); |
| 273 assert(parts.shift() == 'prefs'); | 392 assert(parts.shift() == 'prefs'); |
| 274 | 393 |
| 275 for (let i = 1; i <= parts.length; i++) { | 394 for (let i = 1; i <= parts.length; i++) { |
| 276 let key = parts.slice(0, i).join('.'); | 395 let key = parts.slice(0, i).join('.'); |
| 277 // The lastPrefValues_ keys match the pref keys. | 396 // The lastPrefValues_ keys match the pref keys. |
| 278 if (this.lastPrefValues_.hasOwnProperty(key)) | 397 if (this.lastPrefValues_.hasOwnProperty(key)) |
| 279 return key; | 398 return key; |
| 280 } | 399 } |
| 281 return ''; | 400 return ''; |
| 282 }, | 401 }, |
| 402 |
| 403 /** |
| 404 * Resets the element so it can be re-initialized with a new prefs state. |
| 405 */ |
| 406 resetForTesting: function() { |
| 407 this.prefs = undefined; |
| 408 this.lastPrefValues_ = {}; |
| 409 this.initialized_ = false; |
| 410 }, |
| 283 }); | 411 }); |
| 284 })(); | 412 })(); |
| OLD | NEW |