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

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: 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
22 Polymer({ 28 Polymer({
23 is: 'cr-settings-prefs', 29 is: 'cr-settings-prefs',
24 30
25 properties: { 31 properties: {
26 /** 32 /**
27 * Object containing all preferences. 33 * Object containing all preferences.
28 */ 34 */
29 prefStore: { 35 prefStore: {
30 type: Object, 36 type: Object,
31 value: function() { return {}; }, 37 value: function() { return {}; },
32 notify: true, 38 notify: true,
33 }, 39 },
34 }, 40 },
35 41
36 /** @override */ 42 /** @override */
37 created: function() { 43 created: function() {
38 CrSettingsPrefs.isInitialized = false; 44 CrSettingsPrefs.isInitialized = false;
45 this.committed_ = {};
michaelpg 2015/08/19 02:09:53 Feel free to suggest a better name for "our belief
stevenjb 2015/08/19 17:26:31 It looks like this is a cache of the most recent p
39 46
40 chrome.settingsPrivate.onPrefsChanged.addListener( 47 chrome.settingsPrivate.onPrefsChanged.addListener(
41 this.onPrefsChanged_.bind(this)); 48 this.onPrefsChanged_.bind(this));
42 chrome.settingsPrivate.getAllPrefs(this.onPrefsFetched_.bind(this)); 49 chrome.settingsPrivate.getAllPrefs(this.onPrefsFetched_.bind(this));
43 }, 50 },
44 51
45 /** 52 /**
46 * Called when prefs in the underlying Chrome pref store are changed. 53 * Called when prefs in the underlying Chrome pref store are changed.
47 * @param {!Array<!PrefObject>} prefs The prefs that changed. 54 * @param {!Array<!PrefObject>} prefs The prefs that changed.
48 * @private 55 * @private
49 */ 56 */
50 onPrefsChanged_: function(prefs) { 57 onPrefsChanged_: function(prefs) {
51 this.updatePrefs_(prefs, false); 58 if (CrSettingsPrefs.isInitialized)
59 this.updatePrefs_(prefs);
52 }, 60 },
53 61
54 /** 62 /**
55 * Called when prefs are fetched from settingsPrivate. 63 * Called when prefs are fetched from settingsPrivate.
56 * @param {!Array<!PrefObject>} prefs 64 * @param {!Array<!PrefObject>} prefs
57 * @private 65 * @private
58 */ 66 */
59 onPrefsFetched_: function(prefs) { 67 onPrefsFetched_: function(prefs) {
60 this.updatePrefs_(prefs, true); 68 this.updatePrefs_(prefs);
61 69
62 CrSettingsPrefs.isInitialized = true; 70 CrSettingsPrefs.isInitialized = true;
63 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED)); 71 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED));
64 }, 72 },
65 73
66
67 /** 74 /**
68 * Updates the settings model with the given prefs. 75 * Updates the settings model with the given prefs.
69 * @param {!Array<!PrefObject>} prefs 76 * @param {!Array<!PrefObject>} prefs
70 * @param {boolean} shouldObserve Whether each of the prefs should be
71 * observed.
72 * @private 77 * @private
73 */ 78 */
74 updatePrefs_: function(prefs, shouldObserve) { 79 updatePrefs_: function(prefs) {
75 prefs.forEach(function(prefObj) { 80 prefs.forEach(function(prefObj) {
76 let root = this.prefStore; 81 // Cache a copy of the value so we know it came from settingsPrivate.
77 let tokens = prefObj.key.split('.'); 82 if (prefObj.type == chrome.settingsPrivate.PrefType.LIST)
83 this.committed_[prefObj.key] = prefObj.value.slice();
84 else
85 this.committed_[prefObj.key] = prefObj.value;
Dan Beam 2015/08/19 03:15:38 make a getValue() or getLocalValue() that PrefObje
stevenjb 2015/08/19 17:26:31 Also, this assumes knowledge that prefObj.value is
78 86
79 assert(tokens.length > 0); 87 // Check if the pref exists already.
80 88 var pref = /** @type {(!PrefObject|undefined)} */(
81 for (let i = 0; i < tokens.length; i++) { 89 this.get(prefObj.key, this.prefStore));
82 let token = tokens[i]; 90 if (pref) {
83 91 // Check if the values are different.
Dan Beam 2015/08/19 03:15:38 make a PrefObject#equals() so you can do this rega
84 if (!root.hasOwnProperty(token)) { 92 if (pref.type == chrome.settingsPrivate.PrefType.LIST) {
85 let path = 'prefStore.' + tokens.slice(0, i + 1).join('.'); 93 // Two arrays might have the "same" values, so don't just use "==".
86 this.set(path, {}); 94 if (this.arraysEqual_(pref.value, prefObj.value))
87 } 95 return;
88 root = root[token]; 96 } else if (pref.value == prefObj.value) {
89 } 97 return;
90
91 // NOTE: Do this copy rather than just a re-assignment, so that the
92 // observer fires.
93 for (let objKey in prefObj) {
94 let path = 'prefStore.' + prefObj.key + '.' + objKey;
95
96 // Handle lists specially. We don't want to call this.set()
97 // unconditionally upon updating a list value, since even its contents
98 // are the same as the old list, doing this set() may cause an
99 // infinite update cycle (http://crbug.com/498586).
100 if (objKey == 'value' &&
101 prefObj.type == chrome.settingsPrivate.PrefType.LIST &&
102 !this.shouldUpdateListPrefValue_(root, prefObj['value'])) {
103 continue;
104 } 98 }
105 99
106 this.set(path, prefObj[objKey]); 100 // If we're changing the value to a new array, we need to observe
107 } 101 // the array.
102 if (prefObj.value instanceof Array)
103 this.observeListPrefValue_(prefObj);
Dan Beam 2015/08/19 03:15:38 make PrefObject#observe(callback)
104 this.set('prefStore.' + pref.key + '.value', prefObj.value);
105 } else {
106 let node = this.prefStore;
107 let tokens = prefObj.key.split('.').slice(0, -1);
108 108
109 if (shouldObserve) { 109 // Crawl the pref tree, generating objects where necessary.
110 Object.observe(root, this.propertyChangeCallback_, ['update']); 110 tokens.forEach(function(token) {
Dan Beam 2015/08/19 03:15:38 nit: maybe massage cr.exportPath() take a |target|
stevenjb 2015/08/19 17:26:31 I see that prefs.html includes cr.html, but it doe
Dan Beam 2015/08/19 18:54:11 we could also make a generic merge() or mergeObjec
111 if (!node.hasOwnProperty(token)) {
112 // Don't use Polymer.Base.set because the property update events
113 // won't be useful until the actual pref is set below.
114 node[token] = {};
115 }
116 node = node[token];
117 }, this);
118
119 // Register the observer before this.set(); setting the pref in the
120 // prefStore could trigger a change in prefObj as a side effect.
121 this.observePref_(prefObj);
122 this.set('prefStore.' + prefObj.key, prefObj);
111 } 123 }
112 }, this); 124 }, this);
113 }, 125 },
114 126
127 /**
128 * Registers an observer on the PrefObject using Object.observe.
129 * @param {!PrefObject} prefObj The preference to observe.
130 */
131 observePref_: function(prefObj) {
132 Object.observe(prefObj, this.propertyChangeCallback_.bind(this),
133 ['update']);
134 // If the value is an array, observe the array itself as well.
135 if (prefObj.type == chrome.settingsPrivate.PrefType.LIST)
136 this.observeListPrefValue_(prefObj);
137 },
115 138
116 /** 139 /**
117 * @param {Object} root The root object for a pref that contains a list 140 * Observes the PrefObject's value for PrefObject type LIST.
118 * value. 141 * @param {!PrefObject} prefObj The preference whose value to observe.
119 * @param {!Array} newValue The new list value.
120 * @return {boolean} Whether the new value is different from the one in
121 * root, thus necessitating a pref update.
122 */ 142 */
123 shouldUpdateListPrefValue_: function(root, newValue) { 143 observeListPrefValue_: function(prefObj) {
Dan Beam 2015/08/19 03:15:38 when is this called without observePref_ being tri
124 if (root.value == null || 144 assert(prefObj.type == chrome.settingsPrivate.PrefType.LIST);
125 root.value.length != newValue.length) { 145 Object.observe(prefObj.value,
126 return true; 146 this.arrayChangeCallback_.bind(this, prefObj.key),
127 } 147 ['update', 'splice']);
128
129 for (let i = 0; i < newValue.length; i++) {
130 if (root.value != null && root.value[i] != newValue[i]) {
131 return true;
132 }
133 }
134
135 return false;
136 }, 148 },
137 149
138 /** 150 /**
139 * Called when a property of a pref changes. 151 * Called when a property of a pref changes.
140 * @param {!Array<!Object>} changes An array of objects describing changes. 152 * @param {!Array<!{object: PrefObject, name: string}>} changes
141 * @see http://www.html5rocks.com/en/tutorials/es7/observe/ 153 * Array of change records.
142 * @private 154 * @private
143 */ 155 */
144 propertyChangeCallback_: function(changes) { 156 propertyChangeCallback_: function(changes) {
157 // Rarely, multiple changes could refer to the same value (e.g., toggling
158 // a boolean preference twice), causing redundant work. We could ignore
159 // all but the last such change, but that would muddle the data flow.
145 changes.forEach(function(change) { 160 changes.forEach(function(change) {
146 // UI should only be able to change the value of a setting for now, not 161 // UI should only be able to change the value of a preference for now.
stevenjb 2015/08/19 17:26:31 It's not immediately clear that "value" refers to
147 // disabled, etc.
148 assert(change.name == 'value'); 162 assert(change.name == 'value');
163 let newValue = change.object.value;
164 assert(newValue != undefined);
149 165
150 let newValue = change.object[change.name]; 166 // If settingsPrivate already has this value, do nothing. (Otherwise,
151 assert(newValue !== undefined); 167 // a change event from settingsPrivate could make us call
168 // settingsPrivate.setPref. This can start an infinite IPC loop if there
169 // are multiple Settings windows and the pref is changed.)
170 var prefValue = this.committed_[change.object.key];
171 if (prefValue instanceof Array) {
172 if (this.arraysEqual_(prefValue, newValue))
173 return;
174 } else if (prefValue == newValue) {
175 return;
176 }
177
178 // Save the value we're going to set, so we don't respond to the
179 // change event sent by settingsPrivate once we call setPref.
152 180
153 chrome.settingsPrivate.setPref( 181 chrome.settingsPrivate.setPref(
154 change.object['key'], 182 change.object.key,
155 newValue, 183 newValue,
156 /* pageId */ '', 184 /* pageId */ '',
157 /* callback */ function() {}); 185 /* callback */ verifySetPref);
158 }); 186 }, this);
187 },
188
189 /**
190 * Called when an array value of a pref changes.
191 * @param {string} key The name of the pref.
192 * @param {!Array<{object: !Array}>} changes The list of change records.
193 */
194 arrayChangeCallback_: function(key, changes) {
195 // The underlying array is the same for all changes.
196 var value = changes[0].object;
197
198 // If settingsPrivate already has this value, do nothing.
199 if (this.arraysEqual_(this.committed_[key], value))
200 return;
201 chrome.settingsPrivate.setPref(key, value,
202 /* pageId */ '',
203 /* callback */ verifySetPref);
204 },
205
206 /**
207 * Tests whether two arrays contain the same data (true if primitive
208 * elements are equal and array elements contain the same data).
209 * @param {Array} arr1
210 * @param {Array} arr2
211 * @return {boolean} True if the arrays contain similar values.
212 */
213 arraysEqual_: function(arr1, arr2) {
214 if (!arr1 || !arr2)
215 return arr1 == arr2;
216 if (arr1.length != arr2.length)
217 return false;
218 for (let i = 0; i < arr1.length; i++) {
219 var val1 = arr1[i];
220 var val2 = arr2[i];
221 assert(typeof val1 != 'object' && typeof val2 != 'object',
222 'Objects are not supported.');
223 if (val1 instanceof Array && !this.arraysEqual_(val1, val2))
224 return false;
225 else if (val1 != val2)
226 return false;
227 }
228 return true;
159 }, 229 },
160 }); 230 });
161 })(); 231 })();
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