/* * Copyright (C) 2020 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "PreferenceObserver.h" #import "WebProcessPool.h" #import #import @interface WKUserDefaults : NSUserDefaults { @private RetainPtr m_suiteName; @public WeakObjCPtr m_observer; } - (void)findPreferenceChangesAndNotifyForKeys:(NSDictionary *)oldValues toValuesForKeys:(NSDictionary *)newValues; @end @interface WKPreferenceObserver () { @private Vector> m_userDefaults; } @end @implementation WKUserDefaults - (void)findPreferenceChangesAndNotifyForKeys:(NSDictionary *)oldValues toValuesForKeys:(NSDictionary *)newValues { if (!m_observer) return; for (NSString *key in oldValues) { id oldValue = oldValues[key]; id newValue = newValues[key]; if ([oldValue isEqual:newValue]) continue; if (newValue && ![[newValue class] supportsSecureCoding]) continue; NSString *encodedString = nil; if (newValue) { NSError *e = nil; auto data = retainPtr([NSKeyedArchiver archivedDataWithRootObject:newValue requiringSecureCoding:YES error:&e]); ASSERT(!e); encodedString = [data base64EncodedStringWithOptions:0]; } auto systemValue = adoptCF(CFPreferencesCopyValue((__bridge CFStringRef)key, kCFPreferencesAnyApplication, kCFPreferencesAnyUser, kCFPreferencesAnyHost)); auto globalValue = adoptCF(CFPreferencesCopyValue((__bridge CFStringRef)key, kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost)); auto domainValue = adoptCF(CFPreferencesCopyValue((__bridge CFStringRef)key, (__bridge CFStringRef)m_suiteName.get(), kCFPreferencesCurrentUser, kCFPreferencesAnyHost)); auto preferenceValuesAreEqual = [] (id a, id b) { return a == b || [a isEqual:b]; }; if (preferenceValuesAreEqual((__bridge id)systemValue.get(), newValue) || preferenceValuesAreEqual((__bridge id)globalValue.get(), newValue)) [m_observer preferenceDidChange:nil key:key encodedValue:encodedString]; if (preferenceValuesAreEqual((__bridge id)domainValue.get(), newValue)) [m_observer preferenceDidChange:m_suiteName.get() key:key encodedValue:encodedString]; } } - (void)_notifyObserversOfChangeFromValuesForKeys:(NSDictionary *)oldValues toValuesForKeys:(NSDictionary *)newValues { [super _notifyObserversOfChangeFromValuesForKeys:oldValues toValuesForKeys:newValues]; if (!isMainRunLoop()) { [self findPreferenceChangesAndNotifyForKeys:oldValues toValuesForKeys:newValues]; return; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), [self, protectedSelf = retainPtr(self), oldValues = retainPtr(oldValues), newValues = retainPtr(newValues)] { [self findPreferenceChangesAndNotifyForKeys:oldValues.get() toValuesForKeys:newValues.get()]; }); } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { } - (instancetype)initWithSuiteName:(NSString *)suiteName { if (!(self = [super initWithSuiteName:suiteName])) return nil; m_suiteName = suiteName; m_observer = nil; return self; } @end @implementation WKPreferenceObserver + (id)sharedInstance { static NeverDestroyed> instance = adoptNS([[[self class] alloc] init]); return instance.get().get(); } - (instancetype)init { if (!(self = [super init])) return nil; std::initializer_list domains = { @"com.apple.Accessibility", @"com.apple.mediaaccessibility", #if PLATFORM(IOS_FAMILY) @"com.apple.AdLib", @"com.apple.SpeakSelection", @"com.apple.UIKit", @"com.apple.WebUI", @"com.apple.avfaudio", @"com.apple.itunesstored", @"com.apple.mediaremote", @"com.apple.preferences.sounds", @"com.apple.voiceservices", #else @"com.apple.CFNetwork", @"com.apple.CoreGraphics", @"com.apple.HIToolbox", @"com.apple.ServicesMenu.Services", @"com.apple.ViewBridge", @"com.apple.avfoundation", @"com.apple.avfoundation.videoperformancehud", @"com.apple.driver.AppleBluetoothMultitouch.mouse", @"com.apple.driver.AppleBluetoothMultitouch.trackpad", @"com.apple.speech.voice.prefs", @"com.apple.universalaccess", #endif }; for (auto domain : domains) { auto userDefaults = adoptNS([[WKUserDefaults alloc] initWithSuiteName:domain]); if (!userDefaults) { WTFLogAlways("Could not init user defaults instance for domain %s", String(domain).utf8().data()); continue; } userDefaults.get()->m_observer = self; // Start observing a dummy key in order to make the preference daemon become aware of our NSUserDefaults instance. // This is to make sure we receive KVO notifications. We cannot use normal KVO techniques here, since we are looking // for _any_ changes in a preference domain. For normal KVO techniques to work, we need to provide the specific // key(s) we want to observe, but that set of keys is unknown to us. [userDefaults.get() addObserver:userDefaults.get() forKeyPath:@"testkey" options:NSKeyValueObservingOptionNew context:nil]; m_userDefaults.append(userDefaults); } return self; } - (void)preferenceDidChange:(NSString *)domain key:(NSString *)key encodedValue:(NSString *)encodedValue { #if ENABLE(CFPREFS_DIRECT_MODE) RunLoop::main().dispatch([domain = retainPtr(domain), key = retainPtr(key), encodedValue = retainPtr(encodedValue)] { std::optional encodedString; if (encodedValue) encodedString = String(encodedValue.get()); for (auto& processPool : WebKit::WebProcessPool::allProcessPools()) processPool->notifyPreferencesChanged(domain.get(), key.get(), encodedString); }); #endif } @end