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
3 * found in the LICENSE file. */
7 * 'cr-settings-prefs' models Chrome settings and preferences, listening for
8 * changes to Chrome prefs whitelisted in chrome.settingsPrivate.
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
16 * <cr-settings-prefs prefs="{{prefs}}"></cr-settings-prefs>
17 * <cr-settings-a11y-page prefs="{{prefs}}"></cr-settings-a11y-page>
19 * @group Chrome Settings Elements
20 * @element cr-settings-prefs
27 * Checks whether two values are recursively equal. Only compares serializable
28 * data (primitives, serializable arrays and serializable objects).
29 * @param {*} val1 Value to compare.
30 * @param {*} val2 Value to compare with val1.
31 * @return {boolean} True if the values are recursively equal.
33 function deepEqual(val1, val2) {
37 if (Array.isArray(val1) || Array.isArray(val2)) {
38 if (!Array.isArray(val1) || !Array.isArray(val2))
40 return arraysEqual(/** @type {!Array} */(val1),
41 /** @type {!Array} */(val2));
44 if (val1 instanceof Object && val2 instanceof Object)
45 return objectsEqual(val1, val2);
51 * @param {!Array} arr1
52 * @param {!Array} arr2
53 * @return {boolean} True if the arrays are recursively equal.
55 function arraysEqual(arr1, arr2) {
56 if (arr1.length != arr2.length)
59 for (var i = 0; i < arr1.length; i++) {
60 if (!deepEqual(arr1[i], arr2[i]))
68 * @param {!Object} obj1
69 * @param {!Object} obj2
70 * @return {boolean} True if the objects are recursively equal.
72 function objectsEqual(obj1, obj2) {
73 var keys1 = Object.keys(obj1);
74 var keys2 = Object.keys(obj2);
75 if (keys1.length != keys2.length)
78 for (var i = 0; i < keys1.length; i++) {
80 if (!deepEqual(obj1[key], obj2[key]))
88 * Returns a recursive copy of the value.
89 * @param {*} val Value to copy. Should be a primitive or only contain
90 * serializable data (primitives, serializable arrays and
91 * serializable objects).
92 * @return {*} A deep copy of the value.
94 function deepCopy(val) {
95 if (!(val instanceof Object))
97 return Array.isArray(val) ? deepCopyArray(/** @type {!Array} */(val)) :
102 * @param {!Array} arr
103 * @return {!Array} Deep copy of the array.
105 function deepCopyArray(arr) {
107 for (var i = 0; i < arr.length; i++)
108 copy.push(deepCopy(arr[i]));
113 * @param {!Object} obj
114 * @return {!Object} Deep copy of the object.
116 function deepCopyObject(obj) {
118 var keys = Object.keys(obj);
119 for (var i = 0; i < keys.length; i++) {
121 copy[key] = deepCopy(obj[key]);
127 is: 'cr-settings-prefs',
131 * Object containing all preferences, for use by Polymer controls.
139 * Map of pref keys to values representing the state of the Chrome
140 * pref store as of the last update from the API.
146 value: function() { return {}; },
151 'prefsChanged_(prefs.*)',
154 settingsApi_: chrome.settingsPrivate,
158 // Set window.mockApi to pass a custom settings API, i.e. for tests.
159 // TODO(michaelpg): don't use a global.
161 this.settingsApi_ = window.mockApi;
162 CrSettingsPrefs.isInitialized = false;
164 this.settingsApi_.onPrefsChanged.addListener(
165 this.onSettingsPrivatePrefsChanged_.bind(this));
166 this.settingsApi_.getAllPrefs(
167 this.onSettingsPrivatePrefsFetched_.bind(this));
171 * Polymer callback for changes to this.prefs.
172 * @param {!{path: string, value: *}} change
175 prefsChanged_: function(change) {
176 if (!CrSettingsPrefs.isInitialized)
179 var key = this.getPrefKeyFromPath_(change.path);
180 var prefStoreValue = this.lastPrefValues_[key];
182 var prefObj = /** @type {chrome.settingsPrivate.PrefObject} */(
183 this.get(key, this.prefs));
185 // If settingsPrivate already has this value, do nothing. (Otherwise,
186 // a change event from settingsPrivate could make us call
187 // settingsPrivate.setPref and potentially trigger an IPC loop.)
188 if (deepEqual(prefStoreValue, prefObj.value))
191 this.settingsApi_.setPref(
195 /* callback */ this.setPrefCallback_.bind(this, key));
199 * Called when prefs in the underlying Chrome pref store are changed.
200 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs
201 * The prefs that changed.
204 onSettingsPrivatePrefsChanged_: function(prefs) {
205 if (CrSettingsPrefs.isInitialized)
206 this.updatePrefs_(prefs);
210 * Called when prefs are fetched from settingsPrivate.
211 * @param {!Array<!chrome.settingsPrivate.PrefObject>} prefs
214 onSettingsPrivatePrefsFetched_: function(prefs) {
215 this.updatePrefs_(prefs);
217 CrSettingsPrefs.isInitialized = true;
218 document.dispatchEvent(new Event(CrSettingsPrefs.INITIALIZED));
222 * Checks the result of calling settingsPrivate.setPref.
223 * @param {string} key The key used in the call to setPref.
224 * @param {boolean} success True if setting the pref succeeded.
227 setPrefCallback_: function(key, success) {
231 // Get the current pref value from chrome.settingsPrivate to ensure the
232 // UI stays up to date.
233 this.settingsApi_.getPref(key, function(pref) {
234 this.updatePrefs_([pref]);
239 * Updates the prefs model with the given prefs.
240 * @param {!Array<!chrome.settingsPrivate.PrefObject>} newPrefs
243 updatePrefs_: function(newPrefs) {
244 // Use the existing prefs object or create it.
245 var prefs = this.prefs || {};
246 newPrefs.forEach(function(newPrefObj) {
247 // Use the PrefObject from settingsPrivate to create a copy in
248 // lastPrefValues_ at the pref's key.
249 this.lastPrefValues_[newPrefObj.key] = deepCopy(newPrefObj.value);
251 // Add the pref to |prefs|.
252 cr.exportPath(newPrefObj.key, newPrefObj, prefs);
253 // If this.prefs already exists, notify listeners of the change.
254 if (prefs == this.prefs)
255 this.notifyPath('prefs.' + newPrefObj.key, newPrefObj);
262 * Given a 'property-changed' path, returns the key of the preference the
263 * path refers to. E.g., if the path of the changed property is
264 * 'prefs.search.suggest_enabled.value', the key of the pref that changed is
265 * 'search.suggest_enabled'.
266 * @param {string} path
270 getPrefKeyFromPath_: function(path) {
271 // Skip the first token, which refers to the member variable (this.prefs).
272 var parts = path.split('.');
273 assert(parts.shift() == 'prefs');
275 for (let i = 1; i <= parts.length; i++) {
276 let key = parts.slice(0, i).join('.');
277 // The lastPrefValues_ keys match the pref keys.
278 if (this.lastPrefValues_.hasOwnProperty(key))