| OLD | NEW |
| 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 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 // There are three classes involved here. There's NetworkChangeNotifierMac, | 5 #include "net/base/network_change_notifier_mac.h" |
| 6 // which is the Mac specific implementation of NetworkChangeNotifier. It is the | |
| 7 // class with which clients can register themselves as network change | |
| 8 // observers. There's NetworkChangeNotifierThread, which is a base::Thread | |
| 9 // subclass of MessageLoop::TYPE_UI (since it needs a CFRunLoop) that contains | |
| 10 // the NetworkChangeNotifierImpl. NetworkChangeNotifierImpl is the object | |
| 11 // that receives the actual OS X notifications and posts them to the | |
| 12 // NetworkChangeNotifierMac's message loop, so that NetworkChangeNotifierMac | |
| 13 // can notify all its observers. | |
| 14 // | |
| 15 // When NetworkChangeNotifierMac is being deleted, it will delete the | |
| 16 // NetworkChangeNotifierThread, which will Stop() it and also delete the | |
| 17 // NetworkChangeNotifierImpl. Therefore, NetworkChangeNotifierImpl and | |
| 18 // NetworkChangeNotifierThread's lifetimes generally begin after and end before | |
| 19 // NetworkChangeNotifierMac. There is an edge case where a notification task | |
| 20 // gets posted to the IO thread, thereby maintaining a reference to | |
| 21 // NetworkChangeNotifierImpl beyond the lifetime of NetworkChangeNotifierThread. | |
| 22 // In this case, the notification is cancelled, and NetworkChangeNotifierImpl | |
| 23 // will be deleted once all notification tasks that reference it have been run. | |
| 24 | 6 |
| 25 #include "net/base/network_change_notifier_mac.h" | |
| 26 #include <SystemConfiguration/SCDynamicStore.h> | |
| 27 #include <SystemConfiguration/SCDynamicStoreKey.h> | 7 #include <SystemConfiguration/SCDynamicStoreKey.h> |
| 28 #include <SystemConfiguration/SCSchemaDefinitions.h> | 8 #include <SystemConfiguration/SCSchemaDefinitions.h> |
| 29 #include <algorithm> | 9 #include <algorithm> |
| 30 #include "base/logging.h" | 10 |
| 31 #include "base/message_loop.h" | |
| 32 #include "base/scoped_cftyperef.h" | |
| 33 #include "base/thread.h" | 11 #include "base/thread.h" |
| 34 | 12 |
| 13 // We only post tasks to a child thread we own, so we don't need refcounting. |
| 14 DISABLE_RUNNABLE_METHOD_REFCOUNT(net::NetworkChangeNotifierMac); |
| 15 |
| 35 namespace net { | 16 namespace net { |
| 36 | 17 |
| 37 namespace { | 18 NetworkChangeNotifierMac::NetworkChangeNotifierMac() |
| 38 | 19 : notifier_thread_(new base::Thread("NetworkChangeNotifier")) { |
| 39 // NetworkChangeNotifierImpl should be created on a thread with a CFRunLoop, | 20 // We create this notifier thread because the notification implementation |
| 40 // since it requires one to pump notifications. However, it also runs some | 21 // needs a thread with a CFRunLoop, and there's no guarantee that |
| 41 // methods on |notifier_loop_|, because it cannot post calls to |notifier_| | 22 // MessageLoop::current() meets that criterion. |
| 42 // since NetworkChangeNotifier is not ref counted in a thread safe manner. | 23 base::Thread::Options thread_options(MessageLoop::TYPE_UI, 0); |
| 43 class NetworkChangeNotifierImpl | 24 notifier_thread_->StartWithOptions(thread_options); |
| 44 : public base::RefCountedThreadSafe<NetworkChangeNotifierImpl> { | 25 // TODO(willchan): Look to see if there's a better signal for when it's ok to |
| 45 public: | 26 // initialize this, rather than just delaying it by a fixed time. |
| 46 NetworkChangeNotifierImpl(MessageLoop* notifier_loop, | 27 const int kNotifierThreadInitializationDelayMS = 1000; |
| 47 NetworkChangeNotifierMac* notifier); | 28 notifier_thread_->message_loop()->PostDelayedTask(FROM_HERE, |
| 48 | 29 NewRunnableMethod(this, &NetworkChangeNotifierMac::Init), |
| 49 void Shutdown(); | 30 kNotifierThreadInitializationDelayMS); |
| 50 | |
| 51 private: | |
| 52 friend class base::RefCountedThreadSafe<NetworkChangeNotifierImpl>; | |
| 53 ~NetworkChangeNotifierImpl(); | |
| 54 | |
| 55 static void DynamicStoreCallback(SCDynamicStoreRef /* store */, | |
| 56 CFArrayRef changed_keys, | |
| 57 void* config); | |
| 58 | |
| 59 void OnNetworkConfigChange(CFArrayRef changed_keys); | |
| 60 | |
| 61 // Runs on |notifier_loop_|. | |
| 62 void OnIPAddressChanged(); | |
| 63 | |
| 64 // Raw pointers. Note that |notifier_| _must_ outlive the | |
| 65 // NetworkChangeNotifierImpl. For lifecycle management details, read the | |
| 66 // comment at the top of the file. | |
| 67 MessageLoop* const notifier_loop_; | |
| 68 NetworkChangeNotifierMac* notifier_; | |
| 69 | |
| 70 scoped_cftyperef<CFRunLoopSourceRef> source_; | |
| 71 | |
| 72 DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierImpl); | |
| 73 }; | |
| 74 | |
| 75 NetworkChangeNotifierImpl::NetworkChangeNotifierImpl( | |
| 76 MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier) | |
| 77 : notifier_loop_(notifier_loop), | |
| 78 notifier_(notifier) { | |
| 79 DCHECK_EQ(MessageLoop::TYPE_UI, MessageLoop::current()->type()); | |
| 80 SCDynamicStoreContext context = { | |
| 81 0, // Version 0. | |
| 82 this, // User data. | |
| 83 NULL, // This is not reference counted. No retain function. | |
| 84 NULL, // This is not reference counted. No release function. | |
| 85 NULL, // No description for this. | |
| 86 }; | |
| 87 | |
| 88 // Get a reference to the dynamic store. | |
| 89 scoped_cftyperef<SCDynamicStoreRef> store( | |
| 90 SCDynamicStoreCreate(NULL /* use default allocator */, | |
| 91 CFSTR("org.chromium"), | |
| 92 DynamicStoreCallback, &context)); | |
| 93 | |
| 94 // Create a run loop source for the dynamic store. | |
| 95 source_.reset(SCDynamicStoreCreateRunLoopSource( | |
| 96 NULL /* use default allocator */, | |
| 97 store.get(), | |
| 98 0 /* 0 sounds like a fine source order to me! */)); | |
| 99 | |
| 100 // Add the run loop source to the current run loop. | |
| 101 CFRunLoopAddSource(CFRunLoopGetCurrent(), | |
| 102 source_.get(), | |
| 103 kCFRunLoopCommonModes); | |
| 104 | |
| 105 // Set up the notification keys. | |
| 106 scoped_cftyperef<CFMutableArrayRef> notification_keys( | |
| 107 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); | |
| 108 | |
| 109 // Monitor interface changes. | |
| 110 scoped_cftyperef<CFStringRef> key( | |
| 111 SCDynamicStoreKeyCreateNetworkGlobalEntity( | |
| 112 NULL /* default allocator */, kSCDynamicStoreDomainState, | |
| 113 kSCEntNetInterface)); | |
| 114 CFArrayAppendValue(notification_keys.get(), key.get()); | |
| 115 | |
| 116 // Monitor IP address changes. | |
| 117 | |
| 118 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( | |
| 119 NULL /* default allocator */, kSCDynamicStoreDomainState, | |
| 120 kSCEntNetIPv4)); | |
| 121 CFArrayAppendValue(notification_keys.get(), key.get()); | |
| 122 | |
| 123 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( | |
| 124 NULL /* default allocator */, kSCDynamicStoreDomainState, | |
| 125 kSCEntNetIPv6)); | |
| 126 CFArrayAppendValue(notification_keys.get(), key.get()); | |
| 127 | |
| 128 // Ok, let's ask for notifications! | |
| 129 // TODO(willchan): Figure out a proper way to handle this rather than crash. | |
| 130 CHECK(SCDynamicStoreSetNotificationKeys( | |
| 131 store.get(), notification_keys.get(), NULL)); | |
| 132 } | 31 } |
| 133 | 32 |
| 134 NetworkChangeNotifierImpl::~NetworkChangeNotifierImpl() { | 33 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() { |
| 135 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), | 34 // We don't need to explicitly Stop(), but doing so allows us to sanity- |
| 136 source_.get(), | 35 // check that the notifier thread shut down properly. |
| 137 kCFRunLoopCommonModes); | 36 notifier_thread_->Stop(); |
| 138 } | 37 DCHECK(run_loop_source_ == NULL); |
| 139 | |
| 140 void NetworkChangeNotifierImpl::Shutdown() { | |
| 141 CHECK(notifier_); | |
| 142 notifier_ = NULL; | |
| 143 } | 38 } |
| 144 | 39 |
| 145 // static | 40 // static |
| 146 void NetworkChangeNotifierImpl::DynamicStoreCallback( | 41 void NetworkChangeNotifierMac::DynamicStoreCallback( |
| 147 SCDynamicStoreRef /* store */, | 42 SCDynamicStoreRef /* store */, |
| 148 CFArrayRef changed_keys, | 43 CFArrayRef changed_keys, |
| 149 void* config) { | 44 void* config) { |
| 150 NetworkChangeNotifierImpl* net_config = | 45 NetworkChangeNotifierMac* net_config = |
| 151 static_cast<NetworkChangeNotifierImpl*>(config); | 46 static_cast<NetworkChangeNotifierMac*>(config); |
| 152 net_config->OnNetworkConfigChange(changed_keys); | 47 net_config->OnNetworkConfigChange(changed_keys); |
| 153 } | 48 } |
| 154 | 49 |
| 155 void NetworkChangeNotifierImpl::OnNetworkConfigChange(CFArrayRef changed_keys) { | 50 void NetworkChangeNotifierMac::WillDestroyCurrentMessageLoop() { |
| 51 DCHECK(notifier_thread_ != NULL); |
| 52 // We can't check the notifier_thread_'s message_loop(), as it's now 0. |
| 53 // DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current()); |
| 54 |
| 55 DCHECK(run_loop_source_ != NULL); |
| 56 CFRunLoopRemoveSource(CFRunLoopGetCurrent(), run_loop_source_.get(), |
| 57 kCFRunLoopCommonModes); |
| 58 run_loop_source_.reset(); |
| 59 } |
| 60 |
| 61 void NetworkChangeNotifierMac::Init() { |
| 62 DCHECK(notifier_thread_ != NULL); |
| 63 DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current()); |
| 64 |
| 65 // Add a run loop source for a dynamic store to the current run loop. |
| 66 SCDynamicStoreContext context = { |
| 67 0, // Version 0. |
| 68 this, // User data. |
| 69 NULL, // This is not reference counted. No retain function. |
| 70 NULL, // This is not reference counted. No release function. |
| 71 NULL, // No description for this. |
| 72 }; |
| 73 scoped_cftyperef<SCDynamicStoreRef> store(SCDynamicStoreCreate( |
| 74 NULL, CFSTR("org.chromium"), DynamicStoreCallback, &context)); |
| 75 run_loop_source_.reset(SCDynamicStoreCreateRunLoopSource( |
| 76 NULL, store.get(), 0)); |
| 77 CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source_.get(), |
| 78 kCFRunLoopCommonModes); |
| 79 |
| 80 // Set up notifications for interface and IP address changes. |
| 81 scoped_cftyperef<CFMutableArrayRef> notification_keys( |
| 82 CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks)); |
| 83 scoped_cftyperef<CFStringRef> key(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| 84 NULL, kSCDynamicStoreDomainState, kSCEntNetInterface)); |
| 85 CFArrayAppendValue(notification_keys.get(), key.get()); |
| 86 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| 87 NULL, kSCDynamicStoreDomainState, kSCEntNetIPv4)); |
| 88 CFArrayAppendValue(notification_keys.get(), key.get()); |
| 89 key.reset(SCDynamicStoreKeyCreateNetworkGlobalEntity( |
| 90 NULL, kSCDynamicStoreDomainState, kSCEntNetIPv6)); |
| 91 CFArrayAppendValue(notification_keys.get(), key.get()); |
| 92 |
| 93 // Set the notification keys. This starts us receiving notifications. |
| 94 bool ret = SCDynamicStoreSetNotificationKeys( |
| 95 store.get(), notification_keys.get(), NULL); |
| 96 // TODO(willchan): Figure out a proper way to handle this rather than crash. |
| 97 CHECK(ret); |
| 98 |
| 99 MessageLoop::current()->AddDestructionObserver(this); |
| 100 } |
| 101 |
| 102 void NetworkChangeNotifierMac::OnNetworkConfigChange(CFArrayRef changed_keys) { |
| 103 DCHECK(notifier_thread_ != NULL); |
| 104 DCHECK_EQ(notifier_thread_->message_loop(), MessageLoop::current()); |
| 105 |
| 156 for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) { | 106 for (CFIndex i = 0; i < CFArrayGetCount(changed_keys); ++i) { |
| 157 CFStringRef key = static_cast<CFStringRef>( | 107 CFStringRef key = static_cast<CFStringRef>( |
| 158 CFArrayGetValueAtIndex(changed_keys, i)); | 108 CFArrayGetValueAtIndex(changed_keys, i)); |
| 159 if (CFStringHasSuffix(key, kSCEntNetIPv4) || | 109 if (CFStringHasSuffix(key, kSCEntNetIPv4) || |
| 160 CFStringHasSuffix(key, kSCEntNetIPv6)) { | 110 CFStringHasSuffix(key, kSCEntNetIPv6)) { |
| 161 notifier_loop_->PostTask( | 111 NotifyObserversOfIPAddressChange(); |
| 162 FROM_HERE, | 112 return; |
| 163 NewRunnableMethod( | 113 } |
| 164 this, | 114 if (CFStringHasSuffix(key, kSCEntNetInterface)) { |
| 165 &NetworkChangeNotifierImpl::OnIPAddressChanged)); | |
| 166 } else if (CFStringHasSuffix(key, kSCEntNetInterface)) { | |
| 167 // TODO(willchan): Does not appear to be working. Look into this. | 115 // TODO(willchan): Does not appear to be working. Look into this. |
| 168 // Perhaps this isn't needed anyway. | 116 // Perhaps this isn't needed anyway. |
| 169 } else { | 117 } else { |
| 170 NOTREACHED(); | 118 NOTREACHED(); |
| 171 } | 119 } |
| 172 } | 120 } |
| 173 } | 121 } |
| 174 | 122 |
| 175 void NetworkChangeNotifierImpl::OnIPAddressChanged() { | |
| 176 // If |notifier_| doesn't exist, then that means we're shutting down, so | |
| 177 // notifications are all cancelled. | |
| 178 if (notifier_) | |
| 179 notifier_->OnIPAddressChanged(); | |
| 180 } | |
| 181 | |
| 182 class NetworkChangeNotifierThread : public base::Thread { | |
| 183 public: | |
| 184 NetworkChangeNotifierThread(MessageLoop* notifier_loop, | |
| 185 NetworkChangeNotifierMac* notifier); | |
| 186 ~NetworkChangeNotifierThread(); | |
| 187 | |
| 188 protected: | |
| 189 virtual void Init(); | |
| 190 | |
| 191 private: | |
| 192 MessageLoop* const notifier_loop_; | |
| 193 NetworkChangeNotifierMac* const notifier_; | |
| 194 scoped_refptr<NetworkChangeNotifierImpl> notifier_impl_; | |
| 195 | |
| 196 DISALLOW_COPY_AND_ASSIGN(NetworkChangeNotifierThread); | |
| 197 }; | |
| 198 | |
| 199 NetworkChangeNotifierThread::NetworkChangeNotifierThread( | |
| 200 MessageLoop* notifier_loop, NetworkChangeNotifierMac* notifier) | |
| 201 : base::Thread("NetworkChangeNotifier"), | |
| 202 notifier_loop_(notifier_loop), | |
| 203 notifier_(notifier) {} | |
| 204 | |
| 205 NetworkChangeNotifierThread::~NetworkChangeNotifierThread() { | |
| 206 notifier_impl_->Shutdown(); | |
| 207 Stop(); | |
| 208 } | |
| 209 | |
| 210 // Note that |notifier_impl_| is initialized on the network change | |
| 211 // notifier thread, not whatever thread constructs the | |
| 212 // NetworkChangeNotifierThread object. This is important, because this thread | |
| 213 // is the one that has a CFRunLoop. | |
| 214 void NetworkChangeNotifierThread::Init() { | |
| 215 notifier_impl_ = | |
| 216 new NetworkChangeNotifierImpl(notifier_loop_, notifier_); | |
| 217 } | |
| 218 | |
| 219 } // namespace | |
| 220 | |
| 221 NetworkChangeNotifierMac::NetworkChangeNotifierMac() | |
| 222 : notifier_thread_(NULL), | |
| 223 method_factory_(this) { | |
| 224 // TODO(willchan): Look to see if there's a better signal for when it's ok to | |
| 225 // initialize this, rather than just delaying it by a fixed time. | |
| 226 const int kNotifierThreadInitializationDelayMS = 1000; | |
| 227 MessageLoop* loop = MessageLoop::current(); | |
| 228 loop->PostDelayedTask( | |
| 229 FROM_HERE, | |
| 230 method_factory_.NewRunnableMethod( | |
| 231 &NetworkChangeNotifierMac::InitializeNotifierThread, loop), | |
| 232 kNotifierThreadInitializationDelayMS); | |
| 233 } | |
| 234 | |
| 235 void NetworkChangeNotifierMac::OnIPAddressChanged() { | |
| 236 DCHECK(CalledOnValidThread()); | |
| 237 FOR_EACH_OBSERVER(Observer, observers_, OnIPAddressChanged()); | |
| 238 } | |
| 239 | |
| 240 void NetworkChangeNotifierMac::AddObserver(Observer* observer) { | |
| 241 DCHECK(CalledOnValidThread()); | |
| 242 observers_.AddObserver(observer); | |
| 243 } | |
| 244 | |
| 245 void NetworkChangeNotifierMac::RemoveObserver(Observer* observer) { | |
| 246 DCHECK(CalledOnValidThread()); | |
| 247 observers_.RemoveObserver(observer); | |
| 248 } | |
| 249 | |
| 250 NetworkChangeNotifierMac::~NetworkChangeNotifierMac() { | |
| 251 DCHECK(CalledOnValidThread()); | |
| 252 } | |
| 253 | |
| 254 void NetworkChangeNotifierMac::InitializeNotifierThread(MessageLoop* loop) { | |
| 255 DCHECK(CalledOnValidThread()); | |
| 256 notifier_thread_.reset(new NetworkChangeNotifierThread(loop, this)); | |
| 257 base::Thread::Options thread_options; | |
| 258 thread_options.message_loop_type = MessageLoop::TYPE_UI; | |
| 259 notifier_thread_->StartWithOptions(thread_options); | |
| 260 } | |
| 261 | |
| 262 } // namespace net | 123 } // namespace net |
| OLD | NEW |