blob: 34bfa7925d5d44e0e9e81202479e775815eac6bc [file] [log] [blame]
/*
* 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 <pal/spi/cocoa/NSUserDefaultsSPI.h>
#import <wtf/WeakObjCPtr.h>
@interface WKUserDefaults : NSUserDefaults {
@private
RetainPtr<NSString> m_suiteName;
@public
WeakObjCPtr<WKPreferenceObserver> m_observer;
}
- (void)findPreferenceChangesAndNotifyForKeys:(NSDictionary<NSString *, id> *)oldValues toValuesForKeys:(NSDictionary<NSString *, id> *)newValues;
@end
@interface WKPreferenceObserver () {
@private
Vector<RetainPtr<WKUserDefaults>> m_userDefaults;
}
@end
@implementation WKUserDefaults
- (void)findPreferenceChangesAndNotifyForKeys:(NSDictionary<NSString *, id> *)oldValues toValuesForKeys:(NSDictionary<NSString *, id> *)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 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)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<NSString *, id> *)oldValues toValuesForKeys:(NSDictionary<NSString *, id> *)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<NSKeyValueChangeKey, id> *)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<RetainPtr<WKPreferenceObserver>> instance = adoptNS([[[self class] alloc] init]);
return instance.get().get();
}
- (instancetype)init
{
if (!(self = [super init]))
return nil;
std::initializer_list<NSString*> 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<String> encodedString;
if (encodedValue)
encodedString = String(encodedValue.get());
for (auto& processPool : WebKit::WebProcessPool::allProcessPools())
processPool->notifyPreferencesChanged(domain.get(), key.get(), encodedString);
});
#endif
}
@end