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

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: Update after meeting Created 5 years, 3 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
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' models Chrome settings and preferences, listening for
8 * interaction with settings which are stored in Chrome's 8 * changes to Chrome prefs whitelisted in chrome.settingsPrivate.
9 * Preferences. 9 * When changing prefs in this element's 'prefs' property via the UI, this
10 * element tries to set those preferences in Chrome. Whether or not the calls to
11 * settingsPrivate.setPref succeed, 'prefs' is eventually consistent with the
12 * Chrome pref store.
10 * 13 *
11 * Example: 14 * Example:
12 * 15 *
13 * <cr-settings-prefs id="prefs"></cr-settings-prefs> 16 * <cr-settings-prefs prefs="{{prefs}}"></cr-settings-prefs>
14 * <cr-settings-a11y-page prefs="{{this.$.prefs}}"></cr-settings-a11y-page> 17 * <cr-settings-a11y-page prefs="{{prefs}}"></cr-settings-a11y-page>
15 * 18 *
16 * @group Chrome Settings Elements 19 * @group Chrome Settings Elements
17 * @element cr-settings-a11y-page 20 * @element cr-settings-prefs
18 */ 21 */
19 (function() { 22 (function() {
20 'use strict'; 23 'use strict';
21 24
25 /**
26 * Pref state object. Copies values of PrefObjects received from
27 * settingsPrivate to determine when pref values have changed.
28 * This prototype works for primitive types, but more complex types should
29 * override these functions.
30 * @constructor
Dan Beam 2015/08/28 00:08:41 this might be better as an interface if we can't f
michaelpg 2015/08/28 01:52:47 ? the sane default is == (for all primitive types
31 * @param {!PrefObject} prefObj
Dan Beam 2015/08/26 16:51:33 PrefObject -> PrefData
stevenjb 2015/08/26 17:32:18 What is PrefData? PrefObject is from settingsPriva
Dan Beam 2015/08/26 17:38:01 it's my proposition for a better name
stevenjb 2015/08/26 18:07:09 My point is that't you're proposing a change to an
michaelpg 2015/08/26 23:32:15 I don't see how PrefObject provides more informati
32 */
33 function PrefState(prefObj) {
Dan Beam 2015/08/26 16:51:33 PrefState -> Pref, PrefWrapper
michaelpg 2015/08/26 23:32:15 Done: PrefState -> PrefWrapper.
34 this.value = prefObj.value;
Dan Beam 2015/08/28 00:08:40 why does this have to be public?
michaelpg 2015/08/28 01:52:48 Doesn't. Done.
35 }
36
37 /**
38 * Checks if the value matches this pref's value.
39 * @param {*} value
40 * @return {boolean}
41 */
42 PrefState.prototype.equalsValue = function(value) {
Dan Beam 2015/08/26 16:51:33 PrefState.prototype.equals = function(other) { r
michaelpg 2015/08/26 23:32:15 As I mentioned in a previous comment, PrefState (P
Dan Beam 2015/08/28 00:08:40 construct a PrefWrapper() around the PrefState and
michaelpg 2015/08/28 01:52:48 Done. I didn't have that because it seemed like ex
43 return this.value == value;
Dan Beam 2015/08/28 00:08:40 how do we prevent PrefStates of different types fr
michaelpg 2015/08/28 01:52:48 Prefs are typed in their PrefRegistry as a specifi
44 };
45
46 /**
47 * @constructor
48 * @extends {PrefState}
49 * @param {!PrefObject} prefObj
50 */
51 function ListPrefState(prefObj) {
52 // Copy the array so changes to prefObj aren't reflected in this.value.
53 // TODO(michaelpg): Do a deep copy to support nested lists and objects.
54 this.value = prefObj.value.slice();
55 }
56
57 ListPrefState.__proto__ = PrefState.prototype;
Dan Beam 2015/08/26 16:51:33 ListPrefState.prototype = { __proto__: PrefState
michaelpg 2015/08/26 23:32:15 Done.
58
59 /** @override */
60 ListPrefState.prototype.equalsValue = function(value) {
61 // Two arrays might have the same values, so don't just use "==".
62 return this.arraysEqual_(this.value, value);
63 };
64
65 /**
66 * Tests whether two arrays contain the same data (true if primitive
67 * elements are equal and array elements contain the same data).
68 * @param {Array} arr1
69 * @param {Array} arr2
70 * @return {boolean} True if the arrays contain similar values.
71 */
72 ListPrefState.prototype.arraysEqual_ = function(arr1, arr2) {
Dan Beam 2015/08/28 00:08:40 could this be generalized to work for primitives a
73 if (!arr1 || !arr2)
Dan Beam 2015/08/26 16:51:33 empty arrays are truthy, btw is it valid to get h
stevenjb 2015/08/26 17:32:18 PrefObject.value should never be null, so I believ
michaelpg 2015/08/26 23:32:15 Seems unlikely but it depends on how prefs are str
Dan Beam 2015/08/28 00:08:40 this opens a lot of holes. 0 == [''] for example
74 return arr1 == arr2;
75 if (arr1.length != arr2.length)
76 return false;
77 for (let i = 0; i < arr1.length; i++) {
78 var val1 = arr1[i];
79 var val2 = arr2[i];
80 assert(typeof val1 != 'object' && typeof val2 != 'object',
81 'Objects are not supported.');
82 if (val1 instanceof Array && !this.arraysEqual_(val1, val2))
Dan Beam 2015/08/26 16:51:33 nit: Array.isArray(val1)
michaelpg 2015/08/26 23:32:15 Done.
83 return false;
84 else if (val1 != val2)
85 return false;
86 }
87 return true;
Dan Beam 2015/08/26 16:51:33 why not return arr1.join() == arr2.join(); if
stevenjb 2015/08/26 17:32:18 The C++ coder in me says "Eww". :) I know it's pre
michaelpg 2015/08/26 23:32:15 Not sure what you're going for: the second isn't a
michaelpg 2015/08/26 23:32:15 JSON.stringify doesn't guarantee ordering of objec
Dan Beam 2015/08/28 00:08:40 how does your current code handle arrays of dictio
michaelpg 2015/08/28 01:52:47 It doesn't. So I'll remove this and we can restart
88 };
89
22 Polymer({ 90 Polymer({
23 is: 'cr-settings-prefs', 91 is: 'cr-settings-prefs',
24 92
25 properties: { 93 properties: {
26 /** 94 /**
27 * Object containing all preferences. 95 * Object containing all preferences, for use by Polymer controls.
28 */ 96 */
29 prefStore: { 97 prefs: {
30 type: Object, 98 type: Object,
31 value: function() { return {}; }, 99 value: function() { return {}; },
32 notify: true, 100 notify: true,
33 }, 101 },
102
103 /**
104 * Map of pref keys to PrefState objects representing the state of the
105 * Chrome pref store.
106 * @type {Object<PrefState>}
107 */
108 settingsPrivateState_: {
109 type: Object,
110 value: function() { return {}; },
111 },
34 }, 112 },
35 113
114 observers: [
115 'prefsChanged_(prefs.*)',
116 ],
117
36 /** @override */ 118 /** @override */
37 created: function() { 119 created: function() {
38 CrSettingsPrefs.isInitialized = false; 120 CrSettingsPrefs.isInitialized = false;
39 121
40 chrome.settingsPrivate.onPrefsChanged.addListener( 122 chrome.settingsPrivate.onPrefsChanged.addListener(
41 this.onPrefsChanged_.bind(this)); 123 this.onSettingsPrivatePrefsChanged_.bind(this));
42 chrome.settingsPrivate.getAllPrefs(this.onPrefsFetched_.bind(this)); 124 chrome.settingsPrivate.getAllPrefs(
125 this.onSettingsPrivatePrefsFetched_.bind(this));
43 }, 126 },
44 127
45 /** 128 /**
129 * Polymer callback for changes to this.prefs.
130 * @param {!{path: string, value: *}} change
131 */
132 prefsChanged_: function(change) {
133 if (!CrSettingsPrefs.isInitialized)
134 return;
135
136 var key = this.getPrefKeyFromPath_(change.path);
137 var prefState = this.settingsPrivateState_[key];
138 if (!prefState)
139 return;
140 var prefObj = this.get(key, this.prefs);
141
142 // If settingsPrivate already has this value, do nothing. (Otherwise,
143 // a change event from settingsPrivate could make us call
144 // settingsPrivate.setPref and potentially trigger an IPC loop.)
145 if (!prefState.equalsValue(prefObj.value)) {
stevenjb 2015/08/26 17:32:18 nit: Early exit would better match the comment.
michaelpg 2015/08/26 23:32:15 Done.
146 chrome.settingsPrivate.setPref(
147 key,
148 prefObj.value,
149 /* pageId */ '',
150 /* callback */ this.setPrefCallback_.bind(this, key));
151 }
152 },
153
154 /**
46 * Called when prefs in the underlying Chrome pref store are changed. 155 * Called when prefs in the underlying Chrome pref store are changed.
47 * @param {!Array<!PrefObject>} prefs The prefs that changed. 156 * @param {!Array<!PrefObject>} prefs The prefs that changed.
48 * @private 157 * @private
49 */ 158 */
50 onPrefsChanged_: function(prefs) { 159 onSettingsPrivatePrefsChanged_: function(prefs) {
51 this.updatePrefs_(prefs, false); 160 if (CrSettingsPrefs.isInitialized)
161 this.updatePrefs_(prefs);
52 }, 162 },
53 163
54 /** 164 /**
55 * Called when prefs are fetched from settingsPrivate. 165 * Called when prefs are fetched from settingsPrivate.
56 * @param {!Array<!PrefObject>} prefs 166 * @param {!Array<!PrefObject>} prefs
57 * @private 167 * @private
58 */ 168 */
59 onPrefsFetched_: function(prefs) { 169 onSettingsPrivatePrefsFetched_: function(prefs) {
60 this.updatePrefs_(prefs, true); 170 this.updatePrefs_(prefs);
61 171
62 CrSettingsPrefs.isInitialized = true; 172 CrSettingsPrefs.isInitialized = true;
63 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED)); 173 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED));
64 }, 174 },
65 175
176 /**
177 * Checks the result of calling settingsPrivate.setPref.
178 * @param {string} key The key used in the call to setPref.
179 * @param {boolean} success True if setting the pref succeeded.
180 */
181 setPrefCallback_: function(key, success) {
182 if (!success) {
Dan Beam 2015/08/26 16:51:33 if (success) return; ...
michaelpg 2015/08/26 23:32:15 Done.
183 // Get the current pref value from chrome.settingsPrivate to ensure the
184 // UI stays up to date.
185 chrome.settingsPrivate.getPref(
186 key,
187 function(pref) {
188 assert(pref);
189 this.updatePrefs_([pref]);
190 }.bind(this));
Dan Beam 2015/08/26 16:51:33 chrome.settingsPrivate.getPref(key, function(pref)
michaelpg 2015/08/26 23:32:15 I don't think that's more readable but it's more c
191 }
192 },
66 193
67 /** 194 /**
68 * Updates the settings model with the given prefs. 195 * Updates the prefs model with the given prefs.
69 * @param {!Array<!PrefObject>} prefs 196 * @param {!Array<!PrefObject>} prefs
70 * @param {boolean} shouldObserve Whether each of the prefs should be
71 * observed.
72 * @private 197 * @private
73 */ 198 */
74 updatePrefs_: function(prefs, shouldObserve) { 199 updatePrefs_: function(prefs) {
75 prefs.forEach(function(prefObj) { 200 prefs.forEach(function(newPrefObj) {
76 let root = this.prefStore; 201 // Set or update the PrefState in settingsPrivateState_ with the
77 let tokens = prefObj.key.split('.'); 202 // PrefObject from settingsPrivate.
203 this.setPrefState_(newPrefObj);
78 204
79 assert(tokens.length > 0); 205 // Set or update the pref in |prefs|. This triggers observers in the UI,
80 206 // which update controls associated with the pref.
81 for (let i = 0; i < tokens.length; i++) { 207 this.setPref_(newPrefObj);
82 let token = tokens[i];
83
84 if (!root.hasOwnProperty(token)) {
85 let path = 'prefStore.' + tokens.slice(0, i + 1).join('.');
86 this.set(path, {});
87 }
88 root = root[token];
89 }
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 }
105
106 this.set(path, prefObj[objKey]);
107 }
108
109 if (shouldObserve) {
110 Object.observe(root, this.propertyChangeCallback_, ['update']);
111 }
112 }, this); 208 }, this);
113 }, 209 },
114 210
211 /**
212 * Returns the key of the preference specified by the path. The path may
213 * include tokens after the key, e.g. 'prefs.[key].value'.
214 * @param {string} path
215 * @return {string}
Dan Beam 2015/08/26 16:51:33 all _ -> @private
michaelpg 2015/08/26 23:32:15 Done.
216 */
217 getPrefKeyFromPath_: function(path) {
Dan Beam 2015/08/26 16:51:33 i have no idea what this does, maybe give some con
michaelpg 2015/08/26 23:32:15 Done.
218 var tokens = path.split('.');
Dan Beam 2015/08/26 16:51:33 path.split('.').slice(1);
michaelpg 2015/08/26 23:32:15 Done.
219 var key = '';
115 220
116 /** 221 // Skip the first token, which refers to the member variable (this.prefs).
117 * @param {Object} root The root object for a pref that contains a list 222 for (let i = 1; i < tokens.length; i++) {
118 * value. 223 if (key)
119 * @param {!Array} newValue The new list value. 224 key += '.';
120 * @return {boolean} Whether the new value is different from the one in 225 key += tokens[i];
121 * root, thus necessitating a pref update. 226 // The settingsPrivateState_ keys match the pref keys.
122 */ 227 if (this.settingsPrivateState_[key] != undefined)
123 shouldUpdateListPrefValue_: function(root, newValue) { 228 return key;
124 if (root.value == null ||
125 root.value.length != newValue.length) {
126 return true;
127 } 229 }
230 return '';
231 },
128 232
Dan Beam 2015/08/26 16:51:33 doc
michaelpg 2015/08/26 23:32:15 Done.
129 for (let i = 0; i < newValue.length; i++) { 233 setPref_: function(newPrefObj) {
130 if (root.value != null && root.value[i] != newValue[i]) { 234 // Check if the pref exists already in the Polymer |prefs| object.
131 return true; 235 if (this.get(newPrefObj.key, this.prefs)) {
132 } 236 // Update just the value.
237 this.set('prefs.' + newPrefObj.key + '.value', newPrefObj.value);
238 } else {
239 // Add the pref to |prefs|.
240 let node = this.prefs;
241 let tokens = newPrefObj.key.split('.').slice(0, -1);
242
243 // Crawl the pref tree, generating objects where necessary.
244 tokens.forEach(function(token) {
245 if (!node.hasOwnProperty(token)) {
246 // Don't use Polymer.Base.set because the property update events
247 // won't be useful until the actual pref is set below.
248 node[token] = {};
249 }
250 node = node[token];
251 }, this);
252
stevenjb 2015/08/26 17:32:18 nit: Add comment that notification will occur here
michaelpg 2015/08/26 23:32:15 Done.
253 this.set('prefs.' + newPrefObj.key, newPrefObj);
133 } 254 }
134
135 return false;
136 }, 255 },
137 256
138 /** 257 /**
139 * Called when a property of a pref changes. 258 * Creates a PrefState object from a chrome.settingsPrivate pref and adds it
140 * @param {!Array<!Object>} changes An array of objects describing changes. 259 * to settingsPrivateState_.
141 * @see http://www.html5rocks.com/en/tutorials/es7/observe/ 260 * @param {!PrefObject} PrefObject received from chrome.settingsPrivate.
142 * @private
143 */ 261 */
144 propertyChangeCallback_: function(changes) { 262 setPrefState_: function(prefObj) {
145 changes.forEach(function(change) { 263 var prefState;
146 // UI should only be able to change the value of a setting for now, not 264 if (prefObj.type == chrome.settingsPrivate.PrefType.LIST)
147 // disabled, etc. 265 prefState = new ListPrefState(prefObj);
148 assert(change.name == 'value'); 266 else
149 267 prefState = new PrefState(prefObj);
Dan Beam 2015/08/26 16:51:33 nit: ternary var prefState = prefObj.type == chro
michaelpg 2015/08/26 23:32:15 If we add object prefs, we'd have to change this b
Dan Beam 2015/08/28 00:08:41 we're not yet. do it then
michaelpg 2015/08/28 01:52:47 Done.
150 let newValue = change.object[change.name]; 268 this.settingsPrivateState_[prefObj.key] = prefState;
151 assert(newValue !== undefined);
152
153 chrome.settingsPrivate.setPref(
154 change.object['key'],
155 newValue,
156 /* pageId */ '',
157 /* callback */ function() {});
158 });
159 }, 269 },
160 }); 270 });
161 })(); 271 })();
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698