Index: ios/chrome/app/startup/register_experimental_settings.mm |
diff --git a/ios/chrome/app/startup/register_experimental_settings.mm b/ios/chrome/app/startup/register_experimental_settings.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..be142219f591db1cb6dcf14c19df7231424de373 |
--- /dev/null |
+++ b/ios/chrome/app/startup/register_experimental_settings.mm |
@@ -0,0 +1,128 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "ios/chrome/app/startup/register_experimental_settings.h" |
+ |
+#include "base/logging.h" |
+#include "base/mac/bundle_locations.h" |
+#import "base/mac/scoped_nsobject.h" |
+#include "base/strings/sys_string_conversions.h" |
+ |
+namespace { |
+// Key in the UserDefaults for the Experimental Keys. |
+NSString* kExperimentalKeysKey = @"ExperimentalKeys"; |
+ |
+// Returns YES if a setting value is equivalent to not having the setting at |
+// all. This must always be true for default values, otherwise the experimental |
+// settings will have different default behaviors in stable channel (where the |
+// bundle isn't present). |
+BOOL IsDefaultSettingValueValid(id value) { |
+ if (!value) |
+ return YES; |
+ if ([value isKindOfClass:[NSNumber class]]) |
+ return [value intValue] == 0; |
+ if ([value isKindOfClass:[NSString class]]) |
+ return [value length] == 0; |
+ // Add support for other types as necessary. |
+ NOTREACHED() << "Unhandled value type " |
+ << base::SysNSStringToUTF8(NSStringFromClass([value class])); |
+ return NO; |
+} |
+} // namespace |
+ |
+@interface RegisterExperimentalSettings () |
+// Registers all the default values for a single settings file and returns |
+// all the registered keys. |
++ (NSArray*)registerExperimentalSettingsForFile:(NSString*)filepath |
+ userDefaults:(NSUserDefaults*)userDefaults; |
+@end |
+ |
+@implementation RegisterExperimentalSettings |
+ |
++ (void)registerExperimentalSettingsWithUserDefaults: |
+ (NSUserDefaults*)userDefaults |
+ bundle:(NSBundle*)bundle { |
+ // Save the current app version in user defaults. |
+ NSDictionary* infoDictionary = [bundle infoDictionary]; |
+ NSString* version = [infoDictionary objectForKey:@"CFBundleVersion"]; |
+ [userDefaults setObject:version forKey:@"Version"]; |
+ |
+ NSString* bundlePath = [bundle bundlePath]; |
+ NSString* settingsFilepath = |
+ [bundlePath stringByAppendingPathComponent:@"Settings.bundle"]; |
+ NSArray* settingsContent = |
+ [[NSFileManager defaultManager] contentsOfDirectoryAtPath:settingsFilepath |
+ error:NULL]; |
+ base::scoped_nsobject<NSMutableArray> currentExpKeys( |
+ [[NSMutableArray alloc] init]); |
+ |
+ for (NSString* filename in settingsContent) { |
+ // Only plist files are preferences definition. |
+ if ([[filename pathExtension] isEqualToString:@"plist"]) { |
+ NSString* filepath = |
+ [settingsFilepath stringByAppendingPathComponent:filename]; |
+ NSArray* registeredKeys = |
+ [self registerExperimentalSettingsForFile:filepath |
+ userDefaults:userDefaults]; |
+ [currentExpKeys addObjectsFromArray:registeredKeys]; |
+ } |
+ } |
+ |
+ // Remove all keys that are no longer used. |
+ NSArray* expKeys = [userDefaults arrayForKey:kExperimentalKeysKey]; |
+ NSMutableSet* expKeysSet = [NSMutableSet setWithArray:expKeys]; |
+ NSSet* currentExpKeysSet = [NSSet setWithArray:currentExpKeys]; |
+ [expKeysSet minusSet:currentExpKeysSet]; |
+ for (NSString* key in expKeysSet) { |
+ [userDefaults removeObjectForKey:key]; |
+ } |
+ |
+ if ([currentExpKeys count] > 0) { |
+ [userDefaults setObject:currentExpKeys forKey:kExperimentalKeysKey]; |
+ } else { |
+ [userDefaults removeObjectForKey:kExperimentalKeysKey]; |
+ } |
+} |
+ |
++ (NSArray*)registerExperimentalSettingsForFile:(NSString*)filepath |
+ userDefaults:(NSUserDefaults*)userDefaults { |
+ NSMutableArray* registeredKeys = [NSMutableArray array]; |
+ |
+ NSDictionary* rootDictionary = |
+ [NSDictionary dictionaryWithContentsOfFile:filepath]; |
+ // Array with all the preference specifiers. The plist is composed of many |
+ // Preference specifiers; one for each preference row in the settings |
+ // panel. |
+ NSArray* preferencesArray = |
+ [rootDictionary objectForKey:@"PreferenceSpecifiers"]; |
+ |
+ // Scan thru all the preferences in the plist file. |
+ for (NSDictionary* preferenceSpecifier in preferencesArray) { |
+ NSString* keyValue = [preferenceSpecifier objectForKey:@"Key"]; |
+ if (!keyValue) |
+ continue; |
+ |
+ id defaultValue = [preferenceSpecifier objectForKey:@"DefaultValue"]; |
+ // Within the app, the default for all experimental prefs is nil (matching |
+ // the behavior of Stable channel, where there is no settings bundle). To |
+ // make mistakes obvious, fail if someone tries to set any actual value as |
+ // the default. |
+ DCHECK(IsDefaultSettingValueValid(defaultValue)) |
+ << "'" << base::SysNSStringToUTF8([defaultValue description]) |
+ << "' is not a valid default value for " |
+ << base::SysNSStringToUTF8(keyValue); |
+ |
+ [registeredKeys addObject:keyValue]; |
+ |
+ // If a default value is set, normalize it to nil. |
+ id currentValue = [userDefaults objectForKey:keyValue]; |
+ if (currentValue && |
+ (!defaultValue || [currentValue isEqual:defaultValue])) { |
+ [userDefaults removeObjectForKey:keyValue]; |
+ } |
+ } |
+ return registeredKeys; |
+} |
+ |
+@end |