Bug 1079385 - Send NS_NETWORK_LINK_DATA_CHANGED event on Mac OS X. r=joshmoz
authorDaniel Stenberg <daniel@haxx.se>
Tue, 28 Oct 2014 04:53:00 +0100
changeset 213053 cd5e08af2e43ee726a4081b878b4e280b7f6e564
parent 213052 f31a5e752439f20e94b90255f3a5095d319e89d2
child 213054 b365ed0223370bc2562f1ef3fedd1936bcadcdad
push id51137
push usercbook@mozilla.com
push dateThu, 30 Oct 2014 09:52:34 +0000
treeherdermozilla-inbound@10962f2deab1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjoshmoz
bugs1079385
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1079385 - Send NS_NETWORK_LINK_DATA_CHANGED event on Mac OS X. r=joshmoz
netwerk/system/mac/nsNetworkLinkService.h
netwerk/system/mac/nsNetworkLinkService.mm
--- a/netwerk/system/mac/nsNetworkLinkService.h
+++ b/netwerk/system/mac/nsNetworkLinkService.h
@@ -4,16 +4,17 @@
 
 #ifndef NSNETWORKLINKSERVICEMAC_H_
 #define NSNETWORKLINKSERVICEMAC_H_
 
 #include "nsINetworkLinkService.h"
 #include "nsIObserver.h"
 
 #include <SystemConfiguration/SCNetworkReachability.h>
+#include <SystemConfiguration/SystemConfiguration.h>
 
 class nsNetworkLinkService : public nsINetworkLinkService,
                              public nsIObserver
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSINETWORKLINKSERVICE
     NS_DECL_NSIOBSERVER
@@ -25,19 +26,27 @@ public:
 
 protected:
     virtual ~nsNetworkLinkService();
 
 private:
     bool mLinkUp;
     bool mStatusKnown;
 
+    // Toggles allowing the sending of network-changed event.
+    bool mAllowChangedEvent;
+
     SCNetworkReachabilityRef mReachability;
     CFRunLoopRef mCFRunLoop;
+    CFRunLoopSourceRef mRunLoopSource;
+    SCDynamicStoreRef mStoreRef;
 
     void UpdateReachability();
-    void SendEvent();
+    void SendEvent(bool aNetworkChanged);
     static void ReachabilityChanged(SCNetworkReachabilityRef target,
                                     SCNetworkConnectionFlags flags,
                                     void *info);
+    static void IPConfigChanged(SCDynamicStoreRef store,
+                                CFArrayRef changedKeys,
+                                void *info);
 };
 
 #endif /* NSNETWORKLINKSERVICEMAC_H_ */
--- a/netwerk/system/mac/nsNetworkLinkService.mm
+++ b/netwerk/system/mac/nsNetworkLinkService.mm
@@ -4,29 +4,69 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsNetworkLinkService.h"
 #include "nsCOMPtr.h"
 #include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsCRT.h"
+#include "mozilla/Preferences.h"
 
 #import <Cocoa/Cocoa.h>
 #import <netinet/in.h>
 
+#define NETWORK_NOTIFY_CHANGED_PREF "network.notify.changed"
+
+using namespace mozilla;
+
+// If non-successful, extract the error code and return it.  This
+// error code dance is inspired by
+// http://developer.apple.com/technotes/tn/tn1145.html
+static OSStatus getErrorCodeBool(Boolean success)
+{
+    OSStatus err = noErr;
+    if (!success) {
+        int scErr = ::SCError();
+        if (scErr == kSCStatusOK) {
+            scErr = kSCStatusFailed;
+        }
+        err = scErr;
+    }
+    return err;
+}
+
+// If given a NULL pointer, return the error code.
+static OSStatus getErrorCodePtr(const void *value)
+{
+    return getErrorCodeBool(value != NULL);
+}
+
+// Convenience function to allow NULL input.
+static void CFReleaseSafe(CFTypeRef cf)
+{
+    if (cf) {
+        // "If cf is NULL, this will cause a runtime error and your
+        // application will crash." / Apple docs
+        ::CFRelease(cf);
+    }
+}
+
 NS_IMPL_ISUPPORTS(nsNetworkLinkService,
                   nsINetworkLinkService,
                   nsIObserver)
 
 nsNetworkLinkService::nsNetworkLinkService()
     : mLinkUp(true)
     , mStatusKnown(false)
-    , mReachability(NULL)
-    , mCFRunLoop(NULL)
+    , mAllowChangedEvent(true)
+    , mReachability(nullptr)
+    , mCFRunLoop(nullptr)
+    , mRunLoopSource(nullptr)
+    , mStoreRef(nullptr)
 {
 }
 
 nsNetworkLinkService::~nsNetworkLinkService()
 {
 }
 
 NS_IMETHODIMP
@@ -60,28 +100,42 @@ nsNetworkLinkService::Observe(nsISupport
 {
     if (!strcmp(topic, "xpcom-shutdown")) {
         Shutdown();
     }
 
     return NS_OK;
 }
 
+/* static */
+void
+nsNetworkLinkService::IPConfigChanged(SCDynamicStoreRef aStoreREf,
+                                      CFArrayRef aChangedKeys,
+                                      void *aInfo)
+{
+    nsNetworkLinkService *service =
+        static_cast<nsNetworkLinkService*>(aInfo);
+    service->SendEvent(true);
+}
+
 nsresult
 nsNetworkLinkService::Init(void)
 {
     nsresult rv;
 
     nsCOMPtr<nsIObserverService> observerService =
         do_GetService("@mozilla.org/observer-service;1", &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = observerService->AddObserver(this, "xpcom-shutdown", false);
     NS_ENSURE_SUCCESS(rv, rv);
 
+    Preferences::AddBoolVarCache(&mAllowChangedEvent,
+                                 NETWORK_NOTIFY_CHANGED_PREF, true);
+
     // If the network reachability API can reach 0.0.0.0 without
     // requiring a connection, there is a network interface available.
     struct sockaddr_in addr;
     bzero(&addr, sizeof(addr));
     addr.sin_len = sizeof(addr);
     addr.sin_family = AF_INET;
     mReachability =
         ::SCNetworkReachabilityCreateWithAddress(NULL,
@@ -95,27 +149,90 @@ nsNetworkLinkService::Init(void)
                                             ReachabilityChanged,
                                             &context)) {
         NS_WARNING("SCNetworkReachabilitySetCallback failed.");
         ::CFRelease(mReachability);
         mReachability = NULL;
         return NS_ERROR_NOT_AVAILABLE;
     }
 
+    SCDynamicStoreContext storeContext = {0, this, NULL, NULL, NULL};
+    mStoreRef =
+        ::SCDynamicStoreCreate(NULL,
+                               CFSTR("AddIPAddressListChangeCallbackSCF"),
+                               IPConfigChanged, &storeContext);
+
+    CFStringRef patterns[2] = {NULL, NULL};
+    OSStatus err = getErrorCodePtr(mStoreRef);
+    if (err == noErr) {
+        // This pattern is "State:/Network/Service/[^/]+/IPv4".
+        patterns[0] =
+            ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                          kSCDynamicStoreDomainState,
+                                                          kSCCompAnyRegex,
+                                                          kSCEntNetIPv4);
+        err = getErrorCodePtr(patterns[0]);
+        if (err == noErr) {
+            // This pattern is "State:/Network/Service/[^/]+/IPv6".
+            patterns[1] =
+                ::SCDynamicStoreKeyCreateNetworkServiceEntity(NULL,
+                                                              kSCDynamicStoreDomainState,
+                                                              kSCCompAnyRegex,
+                                                              kSCEntNetIPv6);
+            err = getErrorCodePtr(patterns[1]);
+        }
+    }
+
+    CFArrayRef patternList = NULL;
+    // Create a pattern list containing just one pattern,
+    // then tell SCF that we want to watch changes in keys
+    // that match that pattern list, then create our run loop
+    // source.
+    if (err == noErr) {
+        patternList = ::CFArrayCreate(NULL, (const void **) patterns,
+                                      2, &kCFTypeArrayCallBacks);
+        if (!patternList) {
+            err = -1;
+        }
+    }
+    if (err == noErr) {
+        err =
+            getErrorCodeBool(::SCDynamicStoreSetNotificationKeys(mStoreRef,
+                                                                 NULL,
+                                                                 patternList));
+    }
+
+    if (err == noErr) {
+        mRunLoopSource =
+            ::SCDynamicStoreCreateRunLoopSource(NULL, mStoreRef, 0);
+        err = getErrorCodePtr(mRunLoopSource);
+    }
+
+    CFReleaseSafe(patterns[0]);
+    CFReleaseSafe(patterns[1]);
+    CFReleaseSafe(patternList);
+
+    if (err != noErr) {
+        CFReleaseSafe(mStoreRef);
+        return NS_ERROR_NOT_AVAILABLE;
+    }
+
     // Get the current run loop.  This service is initialized at startup,
     // so we shouldn't run in to any problems with modal dialog run loops.
     mCFRunLoop = [[NSRunLoop currentRunLoop] getCFRunLoop];
     if (!mCFRunLoop) {
         NS_WARNING("Could not get current run loop.");
         ::CFRelease(mReachability);
         mReachability = NULL;
         return NS_ERROR_NOT_AVAILABLE;
     }
     ::CFRetain(mCFRunLoop);
 
+    ::CFRunLoopAddSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
     if (!::SCNetworkReachabilityScheduleWithRunLoop(mReachability, mCFRunLoop,
                                                     kCFRunLoopDefaultMode)) {
         NS_WARNING("SCNetworkReachabilityScheduleWIthRunLoop failed.");
         ::CFRelease(mReachability);
         mReachability = NULL;
         ::CFRelease(mCFRunLoop);
         mCFRunLoop = NULL;
         return NS_ERROR_NOT_AVAILABLE;
@@ -130,21 +247,29 @@ nsresult
 nsNetworkLinkService::Shutdown()
 {
     if (!::SCNetworkReachabilityUnscheduleFromRunLoop(mReachability,
                                                       mCFRunLoop,
                                                       kCFRunLoopDefaultMode)) {
         NS_WARNING("SCNetworkReachabilityUnscheduleFromRunLoop failed.");
     }
 
+    CFRunLoopRemoveSource(mCFRunLoop, mRunLoopSource, kCFRunLoopDefaultMode);
+
     ::CFRelease(mReachability);
-    mReachability = NULL;
+    mReachability = nullptr;
 
     ::CFRelease(mCFRunLoop);
-    mCFRunLoop = NULL;
+    mCFRunLoop = nullptr;
+
+    ::CFRelease(mStoreRef);
+    mStoreRef = nullptr;
+
+    ::CFRelease(mRunLoopSource);
+    mRunLoopSource = nullptr;
 
     return NS_OK;
 }
 
 void
 nsNetworkLinkService::UpdateReachability()
 {
     if (!mReachability) {
@@ -160,39 +285,45 @@ nsNetworkLinkService::UpdateReachability
     bool reachable = (flags & kSCNetworkFlagsReachable) != 0;
     bool needsConnection = (flags & kSCNetworkFlagsConnectionRequired) != 0;
 
     mLinkUp = (reachable && !needsConnection);
     mStatusKnown = true;
 }
 
 void
-nsNetworkLinkService::SendEvent()
+nsNetworkLinkService::SendEvent(bool aNetworkChanged)
 {
     nsCOMPtr<nsIObserverService> observerService =
         do_GetService("@mozilla.org/observer-service;1");
     if (!observerService)
         return;
 
     const char *event;
-    if (!mStatusKnown)
+    if (aNetworkChanged) {
+        if (!mAllowChangedEvent) {
+            return;
+        }
+        event = NS_NETWORK_LINK_DATA_CHANGED;
+    } else if (!mStatusKnown) {
         event = NS_NETWORK_LINK_DATA_UNKNOWN;
-    else
+    } else {
         event = mLinkUp ? NS_NETWORK_LINK_DATA_UP
-                        : NS_NETWORK_LINK_DATA_DOWN;
+            : NS_NETWORK_LINK_DATA_DOWN;
+    }
 
     observerService->NotifyObservers(static_cast<nsINetworkLinkService*>(this),
                                      NS_NETWORK_LINK_TOPIC,
                                      NS_ConvertASCIItoUTF16(event).get());
 }
 
 /* static */
 void
 nsNetworkLinkService::ReachabilityChanged(SCNetworkReachabilityRef target,
                                           SCNetworkConnectionFlags flags,
                                           void *info)
 {
     nsNetworkLinkService *service =
         static_cast<nsNetworkLinkService*>(info);
 
     service->UpdateReachability();
-    service->SendEvent();
+    service->SendEvent(false);
 }