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 * 'settings-prefs' exposes a singleton model of Chrome settings and | 7 * 'settings-prefs' exposes a singleton model of Chrome settings and |
| 8 * preferences, which listens to changes to Chrome prefs whitelisted in | 8 * preferences, which listens to changes to Chrome prefs whitelisted in |
| 9 * chrome.settingsPrivate. When changing prefs in this element's 'prefs' | 9 * chrome.settingsPrivate. When changing prefs in this element's 'prefs' |
| 10 * property via the UI, the singleton model tries to set those preferences in | 10 * property via the UI, the singleton model tries to set those preferences in |
| 11 * Chrome. Whether or not the calls to settingsPrivate.setPref succeed, 'prefs' | 11 * Chrome. Whether or not the calls to settingsPrivate.setPref succeed, 'prefs' |
| 12 * is eventually consistent with the Chrome pref store. | 12 * is eventually consistent with the Chrome pref store. |
| 13 * | |
| 14 * Example: | |
| 15 * | |
| 16 * <settings-prefs prefs="{{prefs}}"></settings-prefs> | |
| 17 * <settings-checkbox pref="{{prefs.homepage_is_newtabpage}}"> | |
| 18 * </settings-checkbox> | |
| 19 */ | 13 */ |
| 20 | 14 |
| 21 (function() { | 15 (function() { |
| 22 'use strict'; | 16 'use strict'; |
| 23 | 17 |
| 24 /** | 18 /** |
| 25 * Checks whether two values are recursively equal. Only compares serializable | 19 * Checks whether two values are recursively equal. Only compares serializable |
| 26 * data (primitives, serializable arrays and serializable objects). | 20 * data (primitives, serializable arrays and serializable objects). |
| 27 * @param {*} val1 Value to compare. | 21 * @param {*} val1 Value to compare. |
| 28 * @param {*} val2 Value to compare with val1. | 22 * @param {*} val2 Value to compare with val1. |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 120 } | 114 } |
| 121 return copy; | 115 return copy; |
| 122 } | 116 } |
| 123 | 117 |
| 124 Polymer({ | 118 Polymer({ |
| 125 is: 'settings-prefs', | 119 is: 'settings-prefs', |
| 126 | 120 |
| 127 properties: { | 121 properties: { |
| 128 /** | 122 /** |
| 129 * Object containing all preferences, for use by Polymer controls. | 123 * Object containing all preferences, for use by Polymer controls. |
| 130 */ | |
| 131 prefs: { | |
| 132 type: Object, | |
| 133 notify: true, | |
| 134 }, | |
| 135 | |
| 136 /** | |
| 137 * Singleton element created at startup which provides the prefs model. | |
| 138 * @type {!Element} | |
| 139 */ | |
| 140 singleton_: { | |
| 141 type: Object, | |
| 142 value: document.createElement('settings-prefs-singleton'), | |
| 143 }, | |
| 144 }, | |
| 145 | |
| 146 observers: [ | |
| 147 'prefsChanged_(prefs.*)', | |
| 148 ], | |
| 149 | |
| 150 /** @override */ | |
| 151 ready: function() { | |
| 152 // Register a callback on CrSettingsPrefs.initialized immediately so prefs | |
| 153 // is set as soon as the settings API returns. This enables other elements | |
| 154 // dependent on |prefs| to add their own callbacks to | |
| 155 // CrSettingsPrefs.initialized. | |
| 156 this.startListening_(); | |
| 157 if (!CrSettingsPrefs.deferInitialization) | |
| 158 this.initialize(); | |
| 159 }, | |
| 160 | |
| 161 /** | |
| 162 * Binds this.prefs to the settings-prefs-singleton's shared prefs once | |
| 163 * preferences are initialized. | |
| 164 * @private | |
| 165 */ | |
| 166 startListening_: function() { | |
| 167 CrSettingsPrefs.initialized.then(function() { | |
| 168 // Ignore changes to prevent prefsChanged_ from notifying singleton_. | |
| 169 this.runWhileIgnoringChanges_(function() { | |
| 170 this.prefs = this.singleton_.prefs; | |
| 171 this.stopListening_(); | |
| 172 this.listen( | |
| 173 this.singleton_, 'prefs-changed', 'singletonPrefsChanged_'); | |
| 174 }); | |
| 175 }.bind(this)); | |
| 176 }, | |
| 177 | |
| 178 /** | |
| 179 * Stops listening for changes to settings-prefs-singleton's shared | |
| 180 * prefs. | |
| 181 * @private | |
| 182 */ | |
| 183 stopListening_: function() { | |
| 184 this.unlisten( | |
| 185 this.singleton_, 'prefs-changed', 'singletonPrefsChanged_'); | |
| 186 }, | |
| 187 | |
| 188 /** | |
| 189 * Handles changes reported by singleton_ by forwarding them to the host. | |
| 190 * @private | |
| 191 */ | |
| 192 singletonPrefsChanged_: function(e) { | |
| 193 // Ignore changes because we've defeated Polymer's dirty-checking. | |
| 194 this.runWhileIgnoringChanges_(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 settings-prefs-singleton. | |
| 202 * @private | |
| 203 */ | |
| 204 prefsChanged_: function(info) { | |
| 205 // Ignore changes that came from singleton_ so we don't re-process | |
| 206 // changes made in other instances of this element. | |
| 207 if (!this.ignoreChanges_) | |
| 208 this.singleton_.fire('shared-prefs-changed', info, {bubbles: false}); | |
| 209 }, | |
| 210 | |
| 211 /** | |
| 212 * Sets ignoreChanged_ before calling the function to suppress change | |
| 213 * events that are manually handled. | |
| 214 * @param {!function()} fn | |
| 215 * @private | |
| 216 */ | |
| 217 runWhileIgnoringChanges_: function(fn) { | |
| 218 assert(!this.ignoreChanges_, | |
| 219 'Nested calls to runWhileIgnoringChanges_ are not supported'); | |
| 220 this.ignoreChanges_ = true; | |
| 221 fn.call(this); | |
| 222 // We can unset ignoreChanges_ now because change notifications | |
| 223 // are synchronous. | |
| 224 this.ignoreChanges_ = false; | |
| 225 }, | |
| 226 | |
| 227 /** Initializes the singleton, which will fetch the prefs. */ | |
| 228 initialize: function() { | |
| 229 this.singleton_.initialize(); | |
| 230 }, | |
| 231 | |
| 232 /** | |
| 233 * Used to initialize the singleton with a fake SettingsPrivate. | |
| 234 * @param {SettingsPrivate} settingsApi Fake implementation to use. | |
| 235 */ | |
| 236 initializeForTesting: function(settingsApi) { | |
| 237 this.singleton_.initialize(settingsApi); | |
| 238 }, | |
| 239 | |
| 240 /** | |
| 241 * Uninitializes this element to remove it from tests. Also resets | |
| 242 * settings-prefs-singleton, allowing newly created elements to | |
| 243 * re-initialize it. | |
| 244 */ | |
| 245 resetForTesting: function() { | |
| 246 this.singleton_.resetForTesting(); | |
| 247 }, | |
| 248 }); | |
| 249 | |
| 250 /** | |
| 251 * Privately used element that contains, listens to and updates the shared | |
| 252 * prefs state. | |
| 253 */ | |
| 254 Polymer({ | |
| 255 is: 'settings-prefs-singleton', | |
| 256 | |
| 257 properties: { | |
| 258 /** | |
| 259 * Object containing all preferences, for use by Polymer controls. | |
| 260 * @type {Object|undefined} | 124 * @type {Object|undefined} |
| 261 */ | 125 */ |
| 262 prefs: { | 126 prefs: { |
| 263 type: Object, | 127 type: Object, |
| 264 notify: true, | 128 notify: true, |
| 265 }, | 129 }, |
| 266 | 130 |
| 267 /** | 131 /** |
| 268 * Map of pref keys to values representing the state of the Chrome | 132 * Map of pref keys to values representing the state of the Chrome |
| 269 * pref store as of the last update from the API. | 133 * pref store as of the last update from the API. |
| 270 * @type {Object<*>} | 134 * @type {Object<*>} |
| 271 * @private | 135 * @private |
| 272 */ | 136 */ |
| 273 lastPrefValues_: { | 137 lastPrefValues_: { |
| 274 type: Object, | 138 type: Object, |
| 275 value: function() { return {}; }, | 139 value: function() { return {}; }, |
| 276 }, | 140 }, |
| 277 }, | 141 }, |
| 278 | 142 |
| 279 // Listen for the manually fired shared-prefs-changed event, fired when | 143 observers: [ |
| 280 // a shared-prefs instance is changed by another element. | 144 'prefsChanged_(prefs.*)', |
| 281 listeners: { | 145 ], |
| 282 'shared-prefs-changed': 'sharedPrefsChanged_', | |
| 283 }, | |
| 284 | 146 |
| 285 /** @type {SettingsPrivate} */ | 147 /** @type {SettingsPrivate} */ |
| 286 settingsApi_: /** @type {SettingsPrivate} */(chrome.settingsPrivate), | 148 settingsApi_: /** @type {SettingsPrivate} */(chrome.settingsPrivate), |
| 287 | 149 |
| 150 created: function() { | |
| 151 if (!CrSettingsPrefs.deferInitialization) | |
| 152 this.initialize(); | |
| 153 }, | |
| 154 | |
| 288 /** | 155 /** |
| 289 * @param {SettingsPrivate=} opt_settingsApi SettingsPrivate implementation | 156 * @param {SettingsPrivate=} opt_settingsApi SettingsPrivate implementation |
| 290 * to use (chrome.settingsPrivate by default). | 157 * to use (chrome.settingsPrivate by default). |
| 291 */ | 158 */ |
| 292 initialize: function(opt_settingsApi) { | 159 initialize: function(opt_settingsApi) { |
| 293 // Only initialize once (or after resetForTesting() is called). | 160 // Only initialize once (or after resetForTesting() is called). |
| 294 if (this.initialized_) | 161 if (this.initialized_) |
| 295 return; | 162 return; |
| 296 this.initialized_ = true; | 163 this.initialized_ = true; |
| 297 | 164 |
| 298 if (opt_settingsApi) | 165 if (opt_settingsApi) |
| 299 this.settingsApi_ = opt_settingsApi; | 166 this.settingsApi_ = opt_settingsApi; |
| 300 | 167 |
| 301 /** @private {function(!Array<!chrome.settingsPrivate.PrefObject>)} */ | 168 /** @private {function(!Array<!chrome.settingsPrivate.PrefObject>)} */ |
| 302 this.boundPrefsChanged_ = this.onSettingsPrivatePrefsChanged_.bind(this); | 169 this.boundPrefsChanged_ = this.onSettingsPrivatePrefsChanged_.bind(this); |
| 303 this.settingsApi_.onPrefsChanged.addListener(this.boundPrefsChanged_); | 170 this.settingsApi_.onPrefsChanged.addListener(this.boundPrefsChanged_); |
| 304 this.settingsApi_.getAllPrefs( | 171 this.settingsApi_.getAllPrefs( |
| 305 this.onSettingsPrivatePrefsFetched_.bind(this)); | 172 this.onSettingsPrivatePrefsFetched_.bind(this)); |
| 306 }, | 173 }, |
| 307 | 174 |
| 308 /** | 175 /** |
| 309 * Polymer callback for changes to prefs.* from a shared-prefs element. | 176 * @param {!{path: string}} e |
| 310 * @param {!CustomEvent} e | |
| 311 * @param {!{path: string}} change | |
| 312 * @private | 177 * @private |
| 313 */ | 178 */ |
| 314 sharedPrefsChanged_: function(e, change) { | 179 prefsChanged_: function(e) { |
| 315 if (!CrSettingsPrefs.isInitialized) | 180 if (!CrSettingsPrefs.isInitialized) |
| 316 return; | 181 return; |
| 182 // Prefs can be set directly in tests. | |
| 183 if (e.path == 'prefs') | |
|
Dan Beam
2016/08/18 05:00:12
nit: maybe combine with above?
michaelpg
2016/08/19 17:25:24
Done.
| |
| 184 return; | |
| 317 | 185 |
| 318 var key = this.getPrefKeyFromPath_(change.path); | 186 var key = this.getPrefKeyFromPath_(e.path); |
| 319 var prefStoreValue = this.lastPrefValues_[key]; | 187 var prefStoreValue = this.lastPrefValues_[key]; |
| 320 | 188 |
| 321 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( | 189 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */( |
| 322 this.get(key, this.prefs)); | 190 this.get(key, this.prefs)); |
| 323 | 191 |
| 324 // If settingsPrivate already has this value, ignore it. (Otherwise, | 192 // If settingsPrivate already has this value, ignore it. (Otherwise, |
| 325 // a change event from settingsPrivate could make us call | 193 // a change event from settingsPrivate could make us call |
| 326 // settingsPrivate.setPref and potentially trigger an IPC loop.) | 194 // settingsPrivate.setPref and potentially trigger an IPC loop.) |
| 327 if (!deepEqual(prefStoreValue, prefObj.value)) { | 195 if (!deepEqual(prefStoreValue, prefObj.value)) { |
| 328 this.settingsApi_.setPref( | 196 this.settingsApi_.setPref( |
| 329 key, | 197 key, |
| 330 prefObj.value, | 198 prefObj.value, |
| 331 /* pageId */ '', | 199 /* pageId */ '', |
| 332 /* callback */ this.setPrefCallback_.bind(this, key)); | 200 /* callback */ this.setPrefCallback_.bind(this, key)); |
| 333 } | 201 } |
| 334 | |
| 335 // Package the event as a prefs-changed event for other elements. | |
| 336 this.fire('prefs-changed', change); | |
| 337 }, | 202 }, |
| 338 | 203 |
| 339 /** | 204 /** |
| 340 * Called when prefs in the underlying Chrome pref store are changed. | 205 * Called when prefs in the underlying Chrome pref store are changed. |
| 341 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs | 206 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs |
| 342 * The prefs that changed. | 207 * The prefs that changed. |
| 343 * @private | 208 * @private |
| 344 */ | 209 */ |
| 345 onSettingsPrivatePrefsChanged_: function(prefs) { | 210 onSettingsPrivatePrefsChanged_: function(prefs) { |
| 346 if (CrSettingsPrefs.isInitialized) | 211 if (CrSettingsPrefs.isInitialized) |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 431 this.prefs = undefined; | 296 this.prefs = undefined; |
| 432 this.lastPrefValues_ = {}; | 297 this.lastPrefValues_ = {}; |
| 433 this.initialized_ = false; | 298 this.initialized_ = false; |
| 434 // Remove the listener added in initialize(). | 299 // Remove the listener added in initialize(). |
| 435 this.settingsApi_.onPrefsChanged.removeListener(this.boundPrefsChanged_); | 300 this.settingsApi_.onPrefsChanged.removeListener(this.boundPrefsChanged_); |
| 436 this.settingsApi_ = | 301 this.settingsApi_ = |
| 437 /** @type {SettingsPrivate} */(chrome.settingsPrivate); | 302 /** @type {SettingsPrivate} */(chrome.settingsPrivate); |
| 438 }, | 303 }, |
| 439 }); | 304 }); |
| 440 })(); | 305 })(); |
| OLD | NEW |