Fix gconf for the linux proxy config service.

-Reenables fetching of settings from gconf.

-Moves all gconf access to happen from the UI thread only, (where
 the default glib main loop runs).

-Adds support for gconf notifications, avoiding having to poll the settings.

-Fixes a small initialization glitch in the unittest. Plus minor code style tweaks.

-Permanently removes gdk and glib threading initialization calls that
 were previously disabled.

-Slight reorganization of ProxyService creation to pass down the IO
 thread MessageLoop.

BUG=11111
TEST=none

Review URL: http://codereview.chromium.org/113043

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16739 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/proxy/proxy_config_service_linux.cc b/net/proxy/proxy_config_service_linux.cc
index cce5835..3421110 100644
--- a/net/proxy/proxy_config_service_linux.cc
+++ b/net/proxy/proxy_config_service_linux.cc
@@ -5,12 +5,12 @@
 #include "net/proxy/proxy_config_service_linux.h"
 
 #include <gconf/gconf-client.h>
-#include <gdk/gdk.h>
 #include <stdlib.h>
 
 #include "base/logging.h"
 #include "base/string_tokenizer.h"
 #include "base/string_util.h"
+#include "base/task.h"
 #include "googleurl/src/url_canon.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_util.h"
@@ -71,8 +71,7 @@
   std::string::size_type at_sign = host.find("@");
   // Should this be supported?
   if (at_sign != std::string::npos) {
-    LOG(ERROR) << "ProxyConfigServiceLinux: proxy authentication "
-        "not supported";
+    LOG(ERROR) << "Proxy authentication not supported";
     // Disregard the authentication parameters and continue with this hostname.
     host = host.substr(at_sign + 1);
   }
@@ -88,7 +87,7 @@
 
 }  // namespace
 
-bool ProxyConfigServiceLinux::GetProxyFromEnvVarForScheme(
+bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVarForScheme(
     const char* variable, ProxyServer::Scheme scheme,
     ProxyServer* result_server) {
   std::string env_value;
@@ -100,21 +99,20 @@
         *result_server = proxy_server;
         return true;
       } else {
-        LOG(ERROR) << "ProxyConfigServiceLinux: failed to parse "
-                   << "environment variable " << variable;
+        LOG(ERROR) << "Failed to parse environment variable " << variable;
       }
     }
   }
   return false;
 }
 
-bool ProxyConfigServiceLinux::GetProxyFromEnvVar(
+bool ProxyConfigServiceLinux::Delegate::GetProxyFromEnvVar(
     const char* variable, ProxyServer* result_server) {
   return GetProxyFromEnvVarForScheme(variable, ProxyServer::SCHEME_HTTP,
                                      result_server);
 }
 
-bool ProxyConfigServiceLinux::GetConfigFromEnv(ProxyConfig* config) {
+bool ProxyConfigServiceLinux::Delegate::GetConfigFromEnv(ProxyConfig* config) {
   // Check for automatic configuration first, in
   // "auto_proxy". Possibly only the "environment_proxy" firefox
   // extension has ever used this, but it still sounds like a good
@@ -160,7 +158,7 @@
     ProxyServer::Scheme scheme = ProxyServer::SCHEME_SOCKS4;
     std::string env_version;
     if (env_var_getter_->Getenv("SOCKS_VERSION", &env_version)
-        && env_version.compare("5") == 0)
+        && env_version == "5")
       scheme = ProxyServer::SCHEME_SOCKS5;
     if (GetProxyFromEnvVarForScheme("SOCKS_SERVER", scheme, &proxy_server)) {
       config->proxy_rules.type = ProxyConfig::ProxyRules::TYPE_SINGLE_PROXY;
@@ -183,37 +181,101 @@
 
 namespace {
 
+// static
+// gconf notification callback, dispatched from the default
+// glib main loop.
+void OnGConfChangeNotification(
+    GConfClient* client, guint cnxn_id,
+    GConfEntry* entry, gpointer user_data) {
+  // It would be nice to debounce multiple callbacks in quick
+  // succession, since I guess we'll get one for each changed key. As
+  // it is we will read settings from gconf once for each callback.
+  LOG(INFO) << "gconf change notification for key "
+            << gconf_entry_get_key(entry);
+  // We don't track which key has changed, just that something did change.
+  // Forward to a method on the proxy config service delegate object.
+  ProxyConfigServiceLinux::Delegate* config_service_delegate =
+      reinterpret_cast<ProxyConfigServiceLinux::Delegate*>(user_data);
+  config_service_delegate->OnCheckProxyConfigSettings();
+}
+
 class GConfSettingGetterImpl
     : public ProxyConfigServiceLinux::GConfSettingGetter {
  public:
-  GConfSettingGetterImpl() : client_(NULL) {}
+  GConfSettingGetterImpl() : client_(NULL), loop_(NULL) {}
+
   virtual ~GConfSettingGetterImpl() {
-    if (client_)
-      g_object_unref(client_);
+    LOG(INFO) << "~GConfSettingGetterImpl called";
+    // client_ should have been released before now, from
+    // Delegate::OnDestroy(), while running on the UI thread.
+    DCHECK(!client_);
   }
 
-  virtual void Enter() {
-    gdk_threads_enter();
-  }
-  virtual void Leave() {
-    gdk_threads_leave();
-  }
-
-  virtual bool InitIfNeeded() {
+  virtual bool Init() {
+    DCHECK(!client_);
+    DCHECK(!loop_);
+    loop_ = MessageLoopForUI::current();
+    client_ = gconf_client_get_default();
     if (!client_) {
-      Enter();
-      client_ = gconf_client_get_default();
-      Leave();
       // It's not clear whether/when this can return NULL.
-      if (!client_)
-        LOG(ERROR) << "ProxyConfigServiceLinux: Unable to create "
-            "a gconf client";
+      LOG(ERROR) << "Unable to create a gconf client";
+      loop_ = NULL;
+      return false;
     }
-    return client_ != NULL;
+    GError* error = NULL;
+    // We need to add the directories for which we'll be asking
+    // notifications, and we might as well ask to preload them.
+    gconf_client_add_dir(client_, "/system/proxy",
+                         GCONF_CLIENT_PRELOAD_ONELEVEL, &error);
+    if (error == NULL) {
+      gconf_client_add_dir(client_, "/system/http_proxy",
+                           GCONF_CLIENT_PRELOAD_ONELEVEL, &error);
+    }
+    if (error != NULL) {
+      LOG(ERROR) << "Error requesting gconf directory: " << error->message;
+      g_error_free(error);
+      Release();
+      return false;
+    }
+    return true;
+  }
+
+  void Release() {
+    if (client_) {
+      DCHECK(MessageLoop::current() == loop_);
+      // This also disables gconf notifications.
+      g_object_unref(client_);
+      client_ = NULL;
+      loop_ = NULL;
+    }
+  }
+
+  bool SetupNotification(void* callback_user_data) {
+    DCHECK(client_);
+    DCHECK(MessageLoop::current() == loop_);
+    GError* error = NULL;
+    gconf_client_notify_add(
+        client_, "/system/proxy",
+        OnGConfChangeNotification, callback_user_data,
+        NULL, &error);
+    if (error == NULL) {
+      gconf_client_notify_add(
+          client_, "/system/http_proxy",
+          OnGConfChangeNotification, callback_user_data,
+          NULL, &error);
+    }
+    if (error != NULL) {
+      LOG(ERROR) << "Error requesting gconf notifications: " << error->message;
+      g_error_free(error);
+      Release();
+      return false;
+    }
+    return true;
   }
 
   virtual bool GetString(const char* key, std::string* result) {
-    CHECK(client_);
+    DCHECK(client_);
+    DCHECK(MessageLoop::current() == loop_);
     GError* error = NULL;
     gchar* value = gconf_client_get_string(client_, key, &error);
     if (HandleGError(error, key))
@@ -225,7 +287,8 @@
     return true;
   }
   virtual bool GetBoolean(const char* key, bool* result) {
-    CHECK(client_);
+    DCHECK(client_);
+    DCHECK(MessageLoop::current() == loop_);
     GError* error = NULL;
     // We want to distinguish unset values from values defaulting to
     // false. For that we need to use the type-generic
@@ -247,7 +310,8 @@
     return true;
   }
   virtual bool GetInt(const char* key, int* result) {
-    CHECK(client_);
+    DCHECK(client_);
+    DCHECK(MessageLoop::current() == loop_);
     GError* error = NULL;
     int value = gconf_client_get_int(client_, key, &error);
     if (HandleGError(error, key))
@@ -259,7 +323,8 @@
   }
   virtual bool GetStringList(const char* key,
                              std::vector<std::string>* result) {
-    CHECK(client_);
+    DCHECK(client_);
+    DCHECK(MessageLoop::current() == loop_);
     GError* error = NULL;
     GSList* list = gconf_client_get_list(client_, key,
                                          GCONF_VALUE_STRING, &error);
@@ -282,8 +347,8 @@
   // (error is NULL).
   bool HandleGError(GError* error, const char* key) {
     if (error != NULL) {
-      LOG(ERROR) << "ProxyConfigServiceLinux: error getting gconf value for "
-                 << key << ": " << error->message;
+      LOG(ERROR) << "Error getting gconf value for " << key
+                 << ": " << error->message;
       g_error_free(error);
       return true;
     }
@@ -292,12 +357,17 @@
 
   GConfClient* client_;
 
+  // Message loop of the thread that we make gconf calls on. It should
+  // be the UI thread and all our methods should be called on this
+  // thread. Only for assertions.
+  MessageLoop* loop_;
+
   DISALLOW_COPY_AND_ASSIGN(GConfSettingGetterImpl);
 };
 
 }  // namespace
 
-bool ProxyConfigServiceLinux::GetProxyFromGConf(
+bool ProxyConfigServiceLinux::Delegate::GetProxyFromGConf(
     const char* key_prefix, bool is_socks, ProxyServer* result_server) {
   std::string key(key_prefix);
   std::string host;
@@ -324,7 +394,8 @@
   return false;
 }
 
-bool ProxyConfigServiceLinux::GetConfigFromGConf(ProxyConfig* config) {
+bool ProxyConfigServiceLinux::Delegate::GetConfigFromGConf(
+    ProxyConfig* config) {
   std::string mode;
   if (!gconf_getter_->GetString("/system/proxy/mode", &mode)) {
     // We expect this to always be set, so if we don't see it then we
@@ -332,11 +403,12 @@
     // proxy config.
     return false;
   }
-  if (mode.compare("none") == 0)
+  if (mode == "none") {
     // Specifically specifies no proxy.
     return true;
+  }
 
-  if (mode.compare("auto") == 0) {
+  if (mode == "auto") {
     // automatic proxy config
     std::string pac_url_str;
     if (gconf_getter_->GetString("/system/proxy/autoconfig_url",
@@ -353,7 +425,7 @@
     return true;
   }
 
-  if (mode.compare("manual") != 0) {
+  if (mode != "manual") {
     // Mode is unrecognized.
     return false;
   }
@@ -419,8 +491,7 @@
   gconf_getter_->GetBoolean("/system/http_proxy/use_authentication",
                             &use_auth);
   if (use_auth)
-    LOG(ERROR) << "ProxyConfigServiceLinux: proxy authentication "
-        "not supported";
+    LOG(ERROR) << "Proxy authentication not supported";
 
   // Now the bypass list.
   gconf_getter_->GetStringList("/system/http_proxy/ignore_hosts",
@@ -431,64 +502,160 @@
   return true;
 }
 
-ProxyConfigServiceLinux::ProxyConfigServiceLinux(
+ProxyConfigServiceLinux::Delegate::Delegate(
     EnvironmentVariableGetter* env_var_getter,
     GConfSettingGetter* gconf_getter)
-    : env_var_getter_(env_var_getter), gconf_getter_(gconf_getter) {
+    : env_var_getter_(env_var_getter), gconf_getter_(gconf_getter),
+      glib_default_loop_(NULL), io_loop_(NULL) {
 }
 
-ProxyConfigServiceLinux::ProxyConfigServiceLinux()
-    : env_var_getter_(new EnvironmentVariableGetterImpl()),
-      gconf_getter_(new GConfSettingGetterImpl()) {
-}
-
-int ProxyConfigServiceLinux::GetProxyConfig(ProxyConfig* config) {
+bool ProxyConfigServiceLinux::Delegate::ShouldTryGConf() {
   // GNOME_DESKTOP_SESSION_ID being defined is a good indication that
   // we are probably running under GNOME.
   // Note: KDE_FULL_SESSION is a corresponding env var to recognize KDE.
   std::string dummy, desktop_session;
-  bool ok = false;
-#if 0  // gconf temporarily disabled because of races.
-       // See http://crbug.com/11442.
-  if (env_var_getter_->Getenv("GNOME_DESKTOP_SESSION_ID", &dummy)
+  return env_var_getter_->Getenv("GNOME_DESKTOP_SESSION_ID", &dummy)
       || (env_var_getter_->Getenv("DESKTOP_SESSION", &desktop_session)
-          && desktop_session.compare("gnome") == 0)) {
-    // Get settings from gconf.
-    //
-    // I (sdoyon) would have liked to prioritize environment variables
-    // and only fallback to gconf if env vars were unset. But
-    // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
-    // does so even if the proxy mode is set to auto, which would
-    // mislead us.
-    //
-    // We could introduce a CHROME_PROXY_OBEY_ENV_VARS variable...??
-    if (gconf_getter_->InitIfNeeded()) {
-      gconf_getter_->Enter();
-      ok = GetConfigFromGConf(config);
-      gconf_getter_->Leave();
-      if (ok)
-        LOG(INFO) << "ProxyConfigServiceLinux: obtained proxy setting "
-            "from gconf";
+          && desktop_session == "gnome");
+  // I (sdoyon) would have liked to prioritize environment variables
+  // and only fallback to gconf if env vars were unset. But
+  // gnome-terminal "helpfully" sets http_proxy and no_proxy, and it
+  // does so even if the proxy mode is set to auto, which would
+  // mislead us.
+  //
+  // We could introduce a CHROME_PROXY_OBEY_ENV_VARS variable...??
+}
+
+void ProxyConfigServiceLinux::Delegate::SetupAndFetchInitialConfig(
+    MessageLoop* glib_default_loop, MessageLoop* io_loop) {
+  // We should be running on the default glib main loop thread right
+  // now. gconf can only be accessed from this thread.
+  DCHECK(MessageLoop::current() == glib_default_loop);
+  glib_default_loop_ = glib_default_loop;
+  io_loop_ = io_loop;
+
+  // If we are passed a NULL io_loop, then we don't setup gconf
+  // notifications. This should not be the usual case but is intended
+  // to simplify test setups.
+  if (!io_loop_)
+    LOG(INFO) << "Monitoring of gconf setting changes is disabled";
+
+  // Fetch and cache the current proxy config. The config is left in
+  // cached_config_, where GetProxyConfig() running on the IO thread
+  // will expect to find it. This is safe to do because we return
+  // before this ProxyConfigServiceLinux is passed on to
+  // the ProxyService.
+  bool got_config = false;
+  if (ShouldTryGConf() &&
+      gconf_getter_->Init() &&
+      (!io_loop || gconf_getter_->SetupNotification(this))) {
+    if (GetConfigFromGConf(&cached_config_)) {
+      cached_config_.set_id(1);  // mark it as valid
+      got_config = true;
+      LOG(INFO) << "Obtained proxy setting from gconf";
       // If gconf proxy mode is "none", meaning direct, then we take
-      // that to be a valid config and will not check environment variables.
-      // The alternative would have been to look for a proxy whereever
-      // we can find one.
+      // that to be a valid config and will not check environment
+      // variables.  The alternative would have been to look for a proxy
+      // where ever we can find one.
       //
-      // TODO(sdoyon): Consider wiring in the gconf notification
-      // system. Cache this result config to return on subsequent calls,
-      // and only call GetConfigFromGConf() when we know things have
-      // actually changed.
+      // Keep a copy of the config for use from this thread for
+      // comparison with updated settings when we get notifications.
+      reference_config_ = cached_config_;
+      reference_config_.set_id(1);  // mark it as valid
+    } else {
+      gconf_getter_->Release();  // Stop notifications
     }
   }
-#endif  // 0 (gconf disabled)
-  // An implementation for KDE settings would be welcome here.
-  if (!ok) {
-    ok = GetConfigFromEnv(config);
-    if (ok)
-      LOG(INFO) << "ProxyConfigServiceLinux: obtained proxy setting "
-          "from environment variables";
+  if (!got_config) {
+    // An implementation for KDE settings would be welcome here.
+    //
+    // Consulting environment variables doesn't need to be done from
+    // the default glib main loop, but it's a tiny enough amount of
+    // work.
+    if (GetConfigFromEnv(&cached_config_)) {
+      cached_config_.set_id(1);  // mark it as valid
+      LOG(INFO) << "Obtained proxy setting from environment variables";
+    }
   }
-  return ok ? OK : ERR_FAILED;
+}
+
+void ProxyConfigServiceLinux::Delegate::Reset() {
+  DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_);
+  gconf_getter_->Release();
+  cached_config_ = ProxyConfig();
+}
+
+int ProxyConfigServiceLinux::Delegate::GetProxyConfig(ProxyConfig* config) {
+  // This is called from the IO thread.
+  DCHECK(!io_loop_ || MessageLoop::current() == io_loop_);
+
+  // Simply return the last proxy configuration that glib_default_loop
+  // notified us of.
+  *config = cached_config_;
+  return cached_config_.is_valid() ? OK : ERR_FAILED;
+}
+
+void ProxyConfigServiceLinux::Delegate::OnCheckProxyConfigSettings() {
+  // This should be dispatched from the thread with the default glib
+  // main loop, which allows us to access gconf.
+  DCHECK(MessageLoop::current() == glib_default_loop_);
+
+  ProxyConfig new_config;
+  bool valid = GetConfigFromGConf(&new_config);
+  if (valid)
+    new_config.set_id(1);  // mark it as valid
+
+  // See if it is different than what we had before.
+  if (new_config.is_valid() != reference_config_.is_valid() ||
+      !new_config.Equals(reference_config_)) {
+    // Post a task to |io_loop| with the new configuration, so it can
+    // update |cached_config_|.
+    io_loop_->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(
+            this,
+            &ProxyConfigServiceLinux::Delegate::SetNewProxyConfig,
+            new_config));
+  }
+}
+
+void ProxyConfigServiceLinux::Delegate::SetNewProxyConfig(
+    const ProxyConfig& new_config) {
+  DCHECK(MessageLoop::current() == io_loop_);
+  LOG(INFO) << "Proxy configuration changed";
+  cached_config_ = new_config;
+}
+
+void ProxyConfigServiceLinux::Delegate::PostDestroyTask() {
+  if (MessageLoop::current() == glib_default_loop_) {
+    // Already on the right thread, call directly.
+    // This is the case for the unittests.
+    OnDestroy();
+  } else {
+    // Post to UI thread. Note that on browser shutdown, we may quit
+    // the UI MessageLoop and exit the program before ever running
+    // this.
+    glib_default_loop_->PostTask(
+        FROM_HERE,
+        NewRunnableMethod(
+            this,
+            &ProxyConfigServiceLinux::Delegate::OnDestroy));
+  }
+}
+void ProxyConfigServiceLinux::Delegate::OnDestroy() {
+  DCHECK(!glib_default_loop_ || MessageLoop::current() == glib_default_loop_);
+  gconf_getter_->Release();
+}
+
+ProxyConfigServiceLinux::ProxyConfigServiceLinux()
+    : delegate_(new Delegate(new EnvironmentVariableGetterImpl(),
+                             new GConfSettingGetterImpl())) {
+}
+
+ProxyConfigServiceLinux::ProxyConfigServiceLinux(
+    EnvironmentVariableGetter* env_var_getter,
+    GConfSettingGetter* gconf_getter)
+    : delegate_(new Delegate(env_var_getter, gconf_getter)) {
 }
 
 }  // namespace net