Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(6477)

Unified Diff: chrome/browser/resources/settings/prefs/prefs.js

Issue 1287913005: Refactor prefs.js for MD-Settings (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Draft Not For Review Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/browser/resources/settings/prefs/prefs.js
diff --git a/chrome/browser/resources/settings/prefs/prefs.js b/chrome/browser/resources/settings/prefs/prefs.js
index b68ccea3c5c9b26ee7608800145cf458189e34af..4f28116267ec65b44de96a6f1e2cb44ac2f3a980 100644
--- a/chrome/browser/resources/settings/prefs/prefs.js
+++ b/chrome/browser/resources/settings/prefs/prefs.js
@@ -19,6 +19,178 @@
(function() {
'use strict';
+ // TODO(michaelpg): Remove once prefs.js is complete and well-tested. Used
+ // to catch errors like crbug.com/521884.
+ var verifySetPref = function(result) {
+ assert(result, 'Failed to set preference.');
+ };
+
+ /**
+ * @constructor
+ * @param {!PrefObject} prefObj
Dan Beam 2015/08/21 17:13:19 PrefObject -> PrefData
michaelpg 2015/08/26 06:02:26 Why? PrefObject is defined in the settings_private
+ */
+ function Pref(prefObj) {
Dan Beam 2015/08/21 17:13:18 prefObj -> data
Dan Beam 2015/08/21 17:13:18 this.data_ = data;
+ this.key = prefObj.key;
+ this.update(prefObj);
Dan Beam 2015/08/21 17:13:18 Object.observe(this.data_, this.dataChanged_.bind(
michaelpg 2015/08/26 06:02:27 Object.observe is being deprecated
+ }
+
+ // Check if the values are different.
+ Pref.prototype.equals = function(oldValue, newValue) {
Dan Beam 2015/08/21 17:13:19 Pref.prototype.equals = function(otherPref) { re
michaelpg 2015/08/26 06:02:26 It's comparing PrefState and PrefObject, so I thin
+ return oldValue == newValue;
+ };
+
+ /**
+ * Update the pref with the value of prefObj when set by settingsPrivate.
+ * @return {boolean} True if the value has changed since last set by Polymer.
+ */
+ Pref.prototype.update = function(prefObj) {
+ // TODO: separate.
+ //
+ this.cachedValue = prefObj.value;
+ if (this.equals(this.value, prefObj.value))
Dan Beam 2015/08/21 17:13:18 if (this.equals(prefObj))
+ return false;
+
+ this.value = prefObj.value;
+ return true;
+ };
+
+ /**
+ * Registers an observer on the PrefObject using Object.observe.
+ * @param {!PrefObject} prefObj The preference to observe.
+ */
+ Pref.prototype.observe = function(prefObj) {
Dan Beam 2015/08/21 17:13:18 Pref.prototype.observe_ = function() { Object.ob
+ Object.observe(prefObj, this.prefChangeCallback_.bind(this),
+ ['update']);
+ };
+
+ /**
+ * Called when a property of a pref changes.
+ * @param {!Array<!{object: PrefObject, name: string}>} changes
+ * Array of change records.
+ * @private
+ */
+ Pref.prototype.prefChangeCallback_ = function(changes) {
+ // Rarely, multiple changes could refer to the same value (e.g., toggling
+ // a boolean preference twice), causing redundant work. We could ignore
+ // all but the last such change, but that would muddle the data flow.
+ changes.forEach(function(change) {
+ // UI should only be able to change the value of a preference.
+ assert(change.name == 'value');
+ let newValue = change.object.value;
+ assert(newValue != undefined);
+
+ // Update the known value for the sake of this.update.
+ this.value = newValue;
+ // (this isn't observed, the original prefObj in prefStore is)
+
+ // If settingsPrivate already has this value, do nothing. (Otherwise,
+ // a change event from settingsPrivate could make us call
+ // settingsPrivate.setPref. This can start an infinite IPC loop if there
+ // are multiple Settings windows and the pref is changed.)
+ // todo
+ if (this.equals(this.cachedValue, newValue))
+ return;
+
+ chrome.settingsPrivate.setPref(
+ this.key,
+ newValue,
+ /* pageId */ '',
+ /* callback */ verifySetPref);
+ }, this);
+ };
+
+ Pref.prototype.observeValue = function(prefObj) {
+ // prefObj is already observed, and value is a primitive.
+ };
+
+ /**
+ * @constructor
+ * @extends {ListPref}
+ * @param {!PrefObject} prefObj
+ */
+ function ListPref(prefObj) {
+ List.call(this, prefObj);
Dan Beam 2015/08/21 17:13:19 Pref.call?
michaelpg 2015/08/26 06:02:26 Changed.
+ }
+
+ ListPref.__proto__ = Pref.prototype;
Dan Beam 2015/08/21 17:13:19 nit: ListPref.prototype = { __proto__: Pref
michaelpg 2015/08/26 06:02:27 OK. I'll take a closer look at this tomorrow.
+
+ /** @override */
+ ListPref.prototype.equals = function(oldValue, newValue) {
+ if (this.type == chrome.settingsPrivate.PrefType.LIST) {
+ // Two arrays might have the "same" values, so don't just use "==".
+ return this.arraysEqual_(oldValue, newValue);
+ }
+ };
+
+ /** @override */
+ ListPref.prototype.observeValue = function(prefObj) {
+ // If the value changed to a new array, observe the new array.
+ Object.observe(prefObj.value,
+ this.valueChangeCallback_.bind(this, this.key),
+ ['update', 'splice']);
+ };
+
+ /** @override */
+ ListPref.prototype.update = function(prefObj) {
+ // Retain a copy of the array for settingsPrivate comparison purposes.
+ this.cachedValue = prefObj.value.slice();
+
+ // functional equivalence
+ var changed = this.equals(this.value, prefObj.value);
+ // identity
+ this.value = prefObj.value;
+ return changed;
+ };
+
+ /**
+ * Called when the array value is changed.
+ * @param {!Array<{object: !Array}>} changes The list of change records.
+ */
+ Pref.prototype.valueChangeCallback_ = function(changes) {
+ // The underlying array is the same for all changes.
+ var value = changes[0].object;
+ // TODO: the array could no longer be the set property...
+
+ // this.value should already refer to the prefObj.value in prefStore,
+ // so nothing to do here
+ // (if the value itself is set to a different array, we'll have observed
+ // that and handled it in ListPref.prototype.prefChangeCallback)
+ // TODO: verify setPref won't be called in both functions (the functions
+ // can't be called on the same change)
+
+ // If settingsPrivate already has this value, do nothing.
+ if (this.arraysEqual_(this.cachedValue, value))
+ return;
+ chrome.settingsPrivate.setPref(this.key, value,
+ /* pageId */ '',
+ /* callback */ verifySetPref);
+ };
+
+ /**
+ * Tests whether two arrays contain the same data (true if primitive
+ * elements are equal and array elements contain the same data).
+ * @param {Array} arr1
+ * @param {Array} arr2
+ * @return {boolean} True if the arrays contain similar values.
+ */
+ ListPref.prototype.arraysEqual_ = function(arr1, arr2) {
Dan Beam 2015/08/21 17:13:19 maybe crazy question, but could ListPref contain a
michaelpg 2015/08/26 06:02:26 Take a look at the new CL and let me know what you
+ if (!arr1 || !arr2)
+ return arr1 == arr2;
+ if (arr1.length != arr2.length)
+ return false;
+ for (let i = 0; i < arr1.length; i++) {
+ var val1 = arr1[i];
+ var val2 = arr2[i];
+ assert(typeof val1 != 'object' && typeof val2 != 'object',
+ 'Objects are not supported.');
+ if (val1 instanceof Array && !this.arraysEqual_(val1, val2))
+ return false;
+ else if (val1 != val2)
+ return false;
+ }
+ return true;
+ };
+
Polymer({
is: 'cr-settings-prefs',
@@ -36,6 +208,9 @@
/** @override */
created: function() {
CrSettingsPrefs.isInitialized = false;
+ // Private counterpart to prefStore, used to interact with
+ // settingsPrivate.
+ this.prefCache_ = {};
Dan Beam 2015/08/21 17:13:18 nit: this.prefs_ or this.state_ if this is long-li
michaelpg 2015/08/26 06:02:27 -> this.settingsPrivateState_
chrome.settingsPrivate.onPrefsChanged.addListener(
this.onPrefsChanged_.bind(this));
@@ -48,7 +223,8 @@
* @private
*/
onPrefsChanged_: function(prefs) {
- this.updatePrefs_(prefs, false);
+ if (CrSettingsPrefs.isInitialized)
+ this.updatePrefs_(prefs);
},
/**
@@ -57,105 +233,72 @@
* @private
*/
onPrefsFetched_: function(prefs) {
- this.updatePrefs_(prefs, true);
+ this.updatePrefs_(prefs);
CrSettingsPrefs.isInitialized = true;
document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED));
},
-
/**
- * Updates the settings model with the given prefs.
+ * Updates the prefs model with the given prefs.
* @param {!Array<!PrefObject>} prefs
- * @param {boolean} shouldObserve Whether each of the prefs should be
- * observed.
* @private
*/
- updatePrefs_: function(prefs, shouldObserve) {
+ updatePrefs_: function(prefs) {
prefs.forEach(function(prefObj) {
- let root = this.prefStore;
- let tokens = prefObj.key.split('.');
+ var cachedPref = this.getCachedPref_(prefObj);
- assert(tokens.length > 0);
+ // Cache a copy of the value from settingsPrivate.
+ var changed = cachedPref.update(prefObj);
- for (let i = 0; i < tokens.length; i++) {
- let token = tokens[i];
+ // Check if the pref exists already in the Polymer pref object.
+ var pref = /** @type {(!PrefObject|undefined)} */(
+ this.get(prefObj.key, this.prefStore));
+ if (pref) {
+ if (cachedPref.equals(prefObj))
+ return;
- if (!root.hasOwnProperty(token)) {
- let path = 'prefStore.' + tokens.slice(0, i + 1).join('.');
- this.set(path, {});
- }
- root = root[token];
- }
+ cachedPref.observeValue(prefObj);
+ this.set('prefStore.' + pref.key + '.value', prefObj.value);
+ } else {
+ let node = this.prefStore;
+ let tokens = prefObj.key.split('.').slice(0, -1);
- // NOTE: Do this copy rather than just a re-assignment, so that the
- // observer fires.
- for (let objKey in prefObj) {
- let path = 'prefStore.' + prefObj.key + '.' + objKey;
-
- // Handle lists specially. We don't want to call this.set()
- // unconditionally upon updating a list value, since even its contents
- // are the same as the old list, doing this set() may cause an
- // infinite update cycle (http://crbug.com/498586).
- if (objKey == 'value' &&
- prefObj.type == chrome.settingsPrivate.PrefType.LIST &&
- !this.shouldUpdateListPrefValue_(root, prefObj['value'])) {
- continue;
- }
-
- this.set(path, prefObj[objKey]);
- }
+ // Crawl the pref tree, generating objects where necessary.
+ tokens.forEach(function(token) {
+ if (!node.hasOwnProperty(token)) {
+ // Don't use Polymer.Base.set because the property update events
+ // won't be useful until the actual pref is set below.
+ node[token] = {};
+ }
+ node = node[token];
+ }, this);
- if (shouldObserve) {
- Object.observe(root, this.propertyChangeCallback_, ['update']);
+ // Register the observer before this.set(); setting the pref in the
+ // prefStore could trigger a change in prefObj as a side effect.
+ cachedPref.observe(prefObj);
+ this.set('prefStore.' + prefObj.key, prefObj);
}
}, this);
},
-
/**
- * @param {Object} root The root object for a pref that contains a list
- * value.
- * @param {!Array} newValue The new list value.
- * @return {boolean} Whether the new value is different from the one in
- * root, thus necessitating a pref update.
+ * @param {!PrefObject} prefObj
*/
- shouldUpdateListPrefValue_: function(root, newValue) {
- if (root.value == null ||
- root.value.length != newValue.length) {
- return true;
- }
-
- for (let i = 0; i < newValue.length; i++) {
- if (root.value != null && root.value[i] != newValue[i]) {
- return true;
- }
- }
+ getCachedPref_: function(prefObj) {
+ var cachedPref = this.prefs_[prefObj.key];
+ if (cachedPref)
+ return cachedPref;
- return false;
- },
+ // Create a cached pref for the PrefObject.
+ if (prefObj.type == chrome.settingsPrivate.PrefType.LIST)
+ cachedPref = new ListPref(prefObj);
+ cachedPref = new Pref(prefObj);
- /**
- * Called when a property of a pref changes.
- * @param {!Array<!Object>} changes An array of objects describing changes.
- * @see http://www.html5rocks.com/en/tutorials/es7/observe/
- * @private
- */
- propertyChangeCallback_: function(changes) {
- changes.forEach(function(change) {
- // UI should only be able to change the value of a setting for now, not
- // disabled, etc.
- assert(change.name == 'value');
-
- let newValue = change.object[change.name];
- assert(newValue !== undefined);
-
- chrome.settingsPrivate.setPref(
- change.object['key'],
- newValue,
- /* pageId */ '',
- /* callback */ function() {});
- });
+ // Set the cached pref.
+ this.prefs_[prefObj.key] = cachedPref;
+ return cachedPref;
},
+
});
})();
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698