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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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' is an element which serves as a model for 7 * 'cr-settings-prefs' is an element which serves as a model for
8 * interaction with settings which are stored in Chrome's 8 * interaction with settings which are stored in Chrome's
9 * Preferences. 9 * Preferences.
10 * 10 *
11 * Example: 11 * Example:
12 * 12 *
13 * <cr-settings-prefs id="prefs"></cr-settings-prefs> 13 * <cr-settings-prefs id="prefs"></cr-settings-prefs>
14 * <cr-settings-a11y-page prefs="{{this.$.prefs}}"></cr-settings-a11y-page> 14 * <cr-settings-a11y-page prefs="{{this.$.prefs}}"></cr-settings-a11y-page>
15 * 15 *
16 * @group Chrome Settings Elements 16 * @group Chrome Settings Elements
17 * @element cr-settings-a11y-page 17 * @element cr-settings-a11y-page
18 */ 18 */
19 (function() { 19 (function() {
20 'use strict'; 20 'use strict';
21 21
22 // TODO(michaelpg): Remove once prefs.js is complete and well-tested. Used
23 // to catch errors like crbug.com/521884.
24 var verifySetPref = function(result) {
25 assert(result, 'Failed to set preference.');
26 };
27
28 /**
29 * @constructor
30 * @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
31 */
32 function Pref(prefObj) {
Dan Beam 2015/08/21 17:13:18 prefObj -> data
Dan Beam 2015/08/21 17:13:18 this.data_ = data;
33 this.key = prefObj.key;
34 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
35 }
36
37 // Check if the values are different.
38 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
39 return oldValue == newValue;
40 };
41
42 /**
43 * Update the pref with the value of prefObj when set by settingsPrivate.
44 * @return {boolean} True if the value has changed since last set by Polymer.
45 */
46 Pref.prototype.update = function(prefObj) {
47 // TODO: separate.
48 //
49 this.cachedValue = prefObj.value;
50 if (this.equals(this.value, prefObj.value))
Dan Beam 2015/08/21 17:13:18 if (this.equals(prefObj))
51 return false;
52
53 this.value = prefObj.value;
54 return true;
55 };
56
57 /**
58 * Registers an observer on the PrefObject using Object.observe.
59 * @param {!PrefObject} prefObj The preference to observe.
60 */
61 Pref.prototype.observe = function(prefObj) {
Dan Beam 2015/08/21 17:13:18 Pref.prototype.observe_ = function() { Object.ob
62 Object.observe(prefObj, this.prefChangeCallback_.bind(this),
63 ['update']);
64 };
65
66 /**
67 * Called when a property of a pref changes.
68 * @param {!Array<!{object: PrefObject, name: string}>} changes
69 * Array of change records.
70 * @private
71 */
72 Pref.prototype.prefChangeCallback_ = function(changes) {
73 // Rarely, multiple changes could refer to the same value (e.g., toggling
74 // a boolean preference twice), causing redundant work. We could ignore
75 // all but the last such change, but that would muddle the data flow.
76 changes.forEach(function(change) {
77 // UI should only be able to change the value of a preference.
78 assert(change.name == 'value');
79 let newValue = change.object.value;
80 assert(newValue != undefined);
81
82 // Update the known value for the sake of this.update.
83 this.value = newValue;
84 // (this isn't observed, the original prefObj in prefStore is)
85
86 // If settingsPrivate already has this value, do nothing. (Otherwise,
87 // a change event from settingsPrivate could make us call
88 // settingsPrivate.setPref. This can start an infinite IPC loop if there
89 // are multiple Settings windows and the pref is changed.)
90 // todo
91 if (this.equals(this.cachedValue, newValue))
92 return;
93
94 chrome.settingsPrivate.setPref(
95 this.key,
96 newValue,
97 /* pageId */ '',
98 /* callback */ verifySetPref);
99 }, this);
100 };
101
102 Pref.prototype.observeValue = function(prefObj) {
103 // prefObj is already observed, and value is a primitive.
104 };
105
106 /**
107 * @constructor
108 * @extends {ListPref}
109 * @param {!PrefObject} prefObj
110 */
111 function ListPref(prefObj) {
112 List.call(this, prefObj);
Dan Beam 2015/08/21 17:13:19 Pref.call?
michaelpg 2015/08/26 06:02:26 Changed.
113 }
114
115 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.
116
117 /** @override */
118 ListPref.prototype.equals = function(oldValue, newValue) {
119 if (this.type == chrome.settingsPrivate.PrefType.LIST) {
120 // Two arrays might have the "same" values, so don't just use "==".
121 return this.arraysEqual_(oldValue, newValue);
122 }
123 };
124
125 /** @override */
126 ListPref.prototype.observeValue = function(prefObj) {
127 // If the value changed to a new array, observe the new array.
128 Object.observe(prefObj.value,
129 this.valueChangeCallback_.bind(this, this.key),
130 ['update', 'splice']);
131 };
132
133 /** @override */
134 ListPref.prototype.update = function(prefObj) {
135 // Retain a copy of the array for settingsPrivate comparison purposes.
136 this.cachedValue = prefObj.value.slice();
137
138 // functional equivalence
139 var changed = this.equals(this.value, prefObj.value);
140 // identity
141 this.value = prefObj.value;
142 return changed;
143 };
144
145 /**
146 * Called when the array value is changed.
147 * @param {!Array<{object: !Array}>} changes The list of change records.
148 */
149 Pref.prototype.valueChangeCallback_ = function(changes) {
150 // The underlying array is the same for all changes.
151 var value = changes[0].object;
152 // TODO: the array could no longer be the set property...
153
154 // this.value should already refer to the prefObj.value in prefStore,
155 // so nothing to do here
156 // (if the value itself is set to a different array, we'll have observed
157 // that and handled it in ListPref.prototype.prefChangeCallback)
158 // TODO: verify setPref won't be called in both functions (the functions
159 // can't be called on the same change)
160
161 // If settingsPrivate already has this value, do nothing.
162 if (this.arraysEqual_(this.cachedValue, value))
163 return;
164 chrome.settingsPrivate.setPref(this.key, value,
165 /* pageId */ '',
166 /* callback */ verifySetPref);
167 };
168
169 /**
170 * Tests whether two arrays contain the same data (true if primitive
171 * elements are equal and array elements contain the same data).
172 * @param {Array} arr1
173 * @param {Array} arr2
174 * @return {boolean} True if the arrays contain similar values.
175 */
176 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
177 if (!arr1 || !arr2)
178 return arr1 == arr2;
179 if (arr1.length != arr2.length)
180 return false;
181 for (let i = 0; i < arr1.length; i++) {
182 var val1 = arr1[i];
183 var val2 = arr2[i];
184 assert(typeof val1 != 'object' && typeof val2 != 'object',
185 'Objects are not supported.');
186 if (val1 instanceof Array && !this.arraysEqual_(val1, val2))
187 return false;
188 else if (val1 != val2)
189 return false;
190 }
191 return true;
192 };
193
22 Polymer({ 194 Polymer({
23 is: 'cr-settings-prefs', 195 is: 'cr-settings-prefs',
24 196
25 properties: { 197 properties: {
26 /** 198 /**
27 * Object containing all preferences. 199 * Object containing all preferences.
28 */ 200 */
29 prefStore: { 201 prefStore: {
30 type: Object, 202 type: Object,
31 value: function() { return {}; }, 203 value: function() { return {}; },
32 notify: true, 204 notify: true,
33 }, 205 },
34 }, 206 },
35 207
36 /** @override */ 208 /** @override */
37 created: function() { 209 created: function() {
38 CrSettingsPrefs.isInitialized = false; 210 CrSettingsPrefs.isInitialized = false;
211 // Private counterpart to prefStore, used to interact with
212 // settingsPrivate.
213 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_
39 214
40 chrome.settingsPrivate.onPrefsChanged.addListener( 215 chrome.settingsPrivate.onPrefsChanged.addListener(
41 this.onPrefsChanged_.bind(this)); 216 this.onPrefsChanged_.bind(this));
42 chrome.settingsPrivate.getAllPrefs(this.onPrefsFetched_.bind(this)); 217 chrome.settingsPrivate.getAllPrefs(this.onPrefsFetched_.bind(this));
43 }, 218 },
44 219
45 /** 220 /**
46 * Called when prefs in the underlying Chrome pref store are changed. 221 * Called when prefs in the underlying Chrome pref store are changed.
47 * @param {!Array<!PrefObject>} prefs The prefs that changed. 222 * @param {!Array<!PrefObject>} prefs The prefs that changed.
48 * @private 223 * @private
49 */ 224 */
50 onPrefsChanged_: function(prefs) { 225 onPrefsChanged_: function(prefs) {
51 this.updatePrefs_(prefs, false); 226 if (CrSettingsPrefs.isInitialized)
227 this.updatePrefs_(prefs);
52 }, 228 },
53 229
54 /** 230 /**
55 * Called when prefs are fetched from settingsPrivate. 231 * Called when prefs are fetched from settingsPrivate.
56 * @param {!Array<!PrefObject>} prefs 232 * @param {!Array<!PrefObject>} prefs
57 * @private 233 * @private
58 */ 234 */
59 onPrefsFetched_: function(prefs) { 235 onPrefsFetched_: function(prefs) {
60 this.updatePrefs_(prefs, true); 236 this.updatePrefs_(prefs);
61 237
62 CrSettingsPrefs.isInitialized = true; 238 CrSettingsPrefs.isInitialized = true;
63 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED)); 239 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED));
64 }, 240 },
65 241
66
67 /** 242 /**
68 * Updates the settings model with the given prefs. 243 * Updates the prefs model with the given prefs.
69 * @param {!Array<!PrefObject>} prefs 244 * @param {!Array<!PrefObject>} prefs
70 * @param {boolean} shouldObserve Whether each of the prefs should be
71 * observed.
72 * @private 245 * @private
73 */ 246 */
74 updatePrefs_: function(prefs, shouldObserve) { 247 updatePrefs_: function(prefs) {
75 prefs.forEach(function(prefObj) { 248 prefs.forEach(function(prefObj) {
76 let root = this.prefStore; 249 var cachedPref = this.getCachedPref_(prefObj);
77 let tokens = prefObj.key.split('.');
78 250
79 assert(tokens.length > 0); 251 // Cache a copy of the value from settingsPrivate.
252 var changed = cachedPref.update(prefObj);
80 253
81 for (let i = 0; i < tokens.length; i++) { 254 // Check if the pref exists already in the Polymer pref object.
82 let token = tokens[i]; 255 var pref = /** @type {(!PrefObject|undefined)} */(
256 this.get(prefObj.key, this.prefStore));
257 if (pref) {
258 if (cachedPref.equals(prefObj))
259 return;
83 260
84 if (!root.hasOwnProperty(token)) { 261 cachedPref.observeValue(prefObj);
85 let path = 'prefStore.' + tokens.slice(0, i + 1).join('.'); 262 this.set('prefStore.' + pref.key + '.value', prefObj.value);
86 this.set(path, {}); 263 } else {
87 } 264 let node = this.prefStore;
88 root = root[token]; 265 let tokens = prefObj.key.split('.').slice(0, -1);
89 }
90 266
91 // NOTE: Do this copy rather than just a re-assignment, so that the 267 // Crawl the pref tree, generating objects where necessary.
92 // observer fires. 268 tokens.forEach(function(token) {
93 for (let objKey in prefObj) { 269 if (!node.hasOwnProperty(token)) {
94 let path = 'prefStore.' + prefObj.key + '.' + objKey; 270 // Don't use Polymer.Base.set because the property update events
271 // won't be useful until the actual pref is set below.
272 node[token] = {};
273 }
274 node = node[token];
275 }, this);
95 276
96 // Handle lists specially. We don't want to call this.set() 277 // Register the observer before this.set(); setting the pref in the
97 // unconditionally upon updating a list value, since even its contents 278 // prefStore could trigger a change in prefObj as a side effect.
98 // are the same as the old list, doing this set() may cause an 279 cachedPref.observe(prefObj);
99 // infinite update cycle (http://crbug.com/498586). 280 this.set('prefStore.' + prefObj.key, prefObj);
100 if (objKey == 'value' &&
101 prefObj.type == chrome.settingsPrivate.PrefType.LIST &&
102 !this.shouldUpdateListPrefValue_(root, prefObj['value'])) {
103 continue;
104 }
105
106 this.set(path, prefObj[objKey]);
107 }
108
109 if (shouldObserve) {
110 Object.observe(root, this.propertyChangeCallback_, ['update']);
111 } 281 }
112 }, this); 282 }, this);
113 }, 283 },
114 284
285 /**
286 * @param {!PrefObject} prefObj
287 */
288 getCachedPref_: function(prefObj) {
289 var cachedPref = this.prefs_[prefObj.key];
290 if (cachedPref)
291 return cachedPref;
115 292
116 /** 293 // Create a cached pref for the PrefObject.
117 * @param {Object} root The root object for a pref that contains a list 294 if (prefObj.type == chrome.settingsPrivate.PrefType.LIST)
118 * value. 295 cachedPref = new ListPref(prefObj);
119 * @param {!Array} newValue The new list value. 296 cachedPref = new Pref(prefObj);
120 * @return {boolean} Whether the new value is different from the one in
121 * root, thus necessitating a pref update.
122 */
123 shouldUpdateListPrefValue_: function(root, newValue) {
124 if (root.value == null ||
125 root.value.length != newValue.length) {
126 return true;
127 }
128 297
129 for (let i = 0; i < newValue.length; i++) { 298 // Set the cached pref.
130 if (root.value != null && root.value[i] != newValue[i]) { 299 this.prefs_[prefObj.key] = cachedPref;
131 return true; 300 return cachedPref;
132 }
133 }
134
135 return false;
136 }, 301 },
137 302
138 /**
139 * Called when a property of a pref changes.
140 * @param {!Array<!Object>} changes An array of objects describing changes.
141 * @see http://www.html5rocks.com/en/tutorials/es7/observe/
142 * @private
143 */
144 propertyChangeCallback_: function(changes) {
145 changes.forEach(function(change) {
146 // UI should only be able to change the value of a setting for now, not
147 // disabled, etc.
148 assert(change.name == 'value');
149
150 let newValue = change.object[change.name];
151 assert(newValue !== undefined);
152
153 chrome.settingsPrivate.setPref(
154 change.object['key'],
155 newValue,
156 /* pageId */ '',
157 /* callback */ function() {});
158 });
159 },
160 }); 303 });
161 })(); 304 })();
OLDNEW
« 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