Add onChange event to preference extension APIs.

BUG=73994
TEST=TBD

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@78354 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_content_settings_apitest.cc b/chrome/browser/extensions/extension_content_settings_apitest.cc
index 2839f3c..c71d5f6 100644
--- a/chrome/browser/extensions/extension_content_settings_apitest.cc
+++ b/chrome/browser/extensions/extension_content_settings_apitest.cc
@@ -74,3 +74,14 @@
   EXPECT_FALSE(pref->IsExtensionControlled());
   EXPECT_EQ(true, pref_service->GetBoolean(prefs::kBlockThirdPartyCookies));
 }
+
+IN_PROC_BROWSER_TEST_F(ExtensionApiTest, ContentSettingsOnChange) {
+  CommandLine::ForCurrentProcess()->AppendSwitch(
+      switches::kEnableExperimentalExtensionApis);
+
+  PrefService* prefs = browser()->profile()->GetPrefs();
+  prefs->SetBoolean(prefs::kBlockThirdPartyCookies, false);
+
+  EXPECT_TRUE(RunExtensionTestIncognito("content_settings/onchange")) <<
+      message_;
+}
diff --git a/chrome/browser/extensions/extension_preference_api.cc b/chrome/browser/extensions/extension_preference_api.cc
index 73e29ce..d4b8410 100644
--- a/chrome/browser/extensions/extension_preference_api.cc
+++ b/chrome/browser/extensions/extension_preference_api.cc
@@ -11,11 +11,14 @@
 #include "base/stl_util-inl.h"
 #include "base/stringprintf.h"
 #include "base/values.h"
+#include "chrome/browser/extensions/extension_event_router.h"
 #include "chrome/browser/extensions/extension_prefs.h"
 #include "chrome/browser/extensions/extension_proxy_api.h"
 #include "chrome/browser/extensions/extension_service.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/common/pref_names.h"
+#include "content/common/notification_type.h"
+#include "content/common/notification_service.h"
 
 namespace {
 
@@ -35,6 +38,8 @@
 const char kLevelOfControl[] = "levelOfControl";
 const char kValue[] = "value";
 
+const char kOnPrefChangeFormat[] = "experimental.preferences.%s.onChange";
+
 const char kIncognitoErrorMessage[] =
     "You do not have permission to access incognito preferences.";
 
@@ -68,6 +73,32 @@
   }
 };
 
+// Returns a string constant (defined in the API) indicating the level of
+// control this extension has over the specified preference.
+const char* GetLevelOfControl(
+    Profile* profile,
+    const std::string& extension_id,
+    const std::string& browser_pref,
+    bool incognito) {
+  PrefService* prefs = incognito ? profile->GetOffTheRecordPrefs()
+                                 : profile->GetPrefs();
+  const PrefService::Preference* pref =
+      prefs->FindPreference(browser_pref.c_str());
+  CHECK(pref);
+  ExtensionPrefs* ep = profile->GetExtensionService()->extension_prefs();
+
+  if (!pref->IsExtensionModifiable())
+    return kNotControllable;
+
+  if (ep->DoesExtensionControlPref(extension_id, browser_pref, incognito))
+    return kControlledByThisExtension;
+
+  if (ep->CanExtensionControlPref(extension_id, browser_pref, incognito))
+    return kControllableByThisExtension;
+
+  return kControlledByOtherExtensions;
+}
+
 class PrefMapping {
  public:
   static PrefMapping* GetInstance() {
@@ -87,6 +118,19 @@
     return false;
   }
 
+  bool FindEventForBrowserPref(const std::string& browser_pref,
+                               std::string* event_name,
+                               std::string* permission) {
+    std::map<std::string, std::pair<std::string, std::string> >::iterator it =
+        event_mapping_.find(browser_pref);
+    if (it != event_mapping_.end()) {
+      *event_name = it->second.first;
+      *permission = it->second.second;
+      return true;
+    }
+    return false;
+  }
+
   PrefTransformerInterface* FindTransformerForBrowserPref(
       const std::string& browser_pref) {
     std::map<std::string, PrefTransformerInterface*>::iterator it =
@@ -106,8 +150,14 @@
       mapping_[kPrefMapping[i].extension_pref] =
           std::make_pair(kPrefMapping[i].browser_pref,
                          kPrefMapping[i].permission);
+      std::string event_name =
+          base::StringPrintf(kOnPrefChangeFormat,
+                             kPrefMapping[i].extension_pref);
+      event_mapping_[kPrefMapping[i].browser_pref] =
+          std::make_pair(event_name, kPrefMapping[i].permission);
     }
     DCHECK_EQ(arraysize(kPrefMapping), mapping_.size());
+    DCHECK_EQ(arraysize(kPrefMapping), event_mapping_.size());
     RegisterPrefTransformer(prefs::kProxy, new ProxyPrefTransformer());
   }
 
@@ -123,44 +173,110 @@
     transformers_[browser_pref] = transformer;
   }
 
-  // Mapping from extension pref keys to browser pref keys.
+  // Mapping from extension pref keys to browser pref keys and permissions.
   std::map<std::string, std::pair<std::string, std::string> > mapping_;
 
+  // Mapping from browser pref keys to extension event names and permissions.
+  std::map<std::string, std::pair<std::string, std::string> > event_mapping_;
 
   // Mapping from browser pref keys to transformers.
   std::map<std::string, PrefTransformerInterface*> transformers_;
 
   scoped_ptr<PrefTransformerInterface> identity_transformer_;
+
+  DISALLOW_COPY_AND_ASSIGN(PrefMapping);
 };
 
 }  // namespace
 
+ExtensionPreferenceEventRouter::ExtensionPreferenceEventRouter(
+    Profile* profile) : profile_(profile) {
+  registrar_.Init(profile_->GetPrefs());
+  incognito_registrar_.Init(profile_->GetOffTheRecordPrefs());
+  for (size_t i = 0; i < arraysize(kPrefMapping); ++i) {
+    registrar_.Add(kPrefMapping[i].browser_pref, this);
+    incognito_registrar_.Add(kPrefMapping[i].browser_pref, this);
+  }
+}
+
+ExtensionPreferenceEventRouter::~ExtensionPreferenceEventRouter() { }
+
+void ExtensionPreferenceEventRouter::Observe(
+    NotificationType type,
+    const NotificationSource& source,
+    const NotificationDetails& details) {
+  if (type == NotificationType::PREF_CHANGED) {
+    const std::string* pref_key =
+        Details<const std::string>(details).ptr();
+    OnPrefChanged(Source<PrefService>(source).ptr(), *pref_key);
+  } else {
+    NOTREACHED();
+  }
+}
+
+void ExtensionPreferenceEventRouter::OnPrefChanged(
+    PrefService* pref_service,
+    const std::string& browser_pref) {
+  bool incognito = (pref_service != profile_->GetPrefs());
+
+  std::string event_name;
+  std::string permission;
+  bool rv = PrefMapping::GetInstance()->FindEventForBrowserPref(
+      browser_pref, &event_name, &permission);
+  DCHECK(rv);
+
+  ListValue args;
+  DictionaryValue* dict = new DictionaryValue();
+  args.Append(dict);
+  const PrefService::Preference* pref =
+      pref_service->FindPreference(browser_pref.c_str());
+  CHECK(pref);
+  ExtensionService* extension_service = profile_->GetExtensionService();
+  PrefTransformerInterface* transformer =
+      PrefMapping::GetInstance()->FindTransformerForBrowserPref(browser_pref);
+  dict->Set(kValue, transformer->BrowserToExtensionPref(pref->GetValue()));
+  if (incognito) {
+    ExtensionPrefs* ep = extension_service->extension_prefs();
+    dict->Set(
+        kIncognitoSpecific,
+        Value::CreateBooleanValue(ep->HasIncognitoPrefValue(browser_pref)));
+  }
+
+  ExtensionEventRouter* router = profile_->GetExtensionEventRouter();
+  if (!router || !router->HasEventListener(event_name))
+    return;
+  const ExtensionList* extensions = extension_service->extensions();
+  for (ExtensionList::const_iterator it = extensions->begin();
+       it != extensions->end(); ++it) {
+    std::string extension_id = (*it)->id();
+    // TODO(bauerb): Only iterate over registered event listeners.
+    if (router->ExtensionHasEventListener(extension_id, event_name) &&
+        (*it)->HasApiPermission(permission) &&
+        (!incognito || extension_service->CanCrossIncognito(*it))) {
+      std::string level_of_control =
+          GetLevelOfControl(profile_, extension_id, browser_pref, incognito);
+      dict->Set(kLevelOfControl, Value::CreateStringValue(level_of_control));
+
+      std::string json_args;
+      base::JSONWriter::Write(&args, false, &json_args);
+
+      DispatchEvent(extension_id, event_name, json_args);
+    }
+  }
+}
+
+void ExtensionPreferenceEventRouter::DispatchEvent(
+    const std::string& extension_id,
+    const std::string& event_name,
+    const std::string& json_args) {
+  profile_->GetExtensionEventRouter()->DispatchEventToExtension(
+      extension_id, event_name, json_args, NULL, GURL());
+}
+
 // TODO(battre): Factor out common parts once this is stable.
 
 GetPreferenceFunction::~GetPreferenceFunction() { }
 
-const char* GetPreferenceFunction::GetLevelOfControl(
-    const std::string& browser_pref,
-    bool incognito) const {
-  PrefService* prefs = incognito ? profile_->GetOffTheRecordPrefs()
-                                 : profile_->GetPrefs();
-  const PrefService::Preference* pref =
-      prefs->FindPreference(browser_pref.c_str());
-  CHECK(pref);
-  ExtensionPrefs* ep = profile_->GetExtensionService()->extension_prefs();
-
-  if (!pref->IsExtensionModifiable())
-    return kNotControllable;
-
-  if (ep->DoesExtensionControlPref(extension_id(), browser_pref, incognito))
-    return kControlledByThisExtension;
-
-  if (ep->CanExtensionControlPref(extension_id(), browser_pref, incognito))
-    return kControllableByThisExtension;
-
-  return kControlledByOtherExtensions;
-}
-
 bool GetPreferenceFunction::RunImpl() {
   std::string pref_key;
   EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &pref_key));
@@ -191,7 +307,8 @@
   const PrefService::Preference* pref =
       prefs->FindPreference(browser_pref.c_str());
   CHECK(pref);
-  std::string level_of_control = GetLevelOfControl(browser_pref, incognito);
+  std::string level_of_control =
+      GetLevelOfControl(profile_, extension_id(), browser_pref, incognito);
 
   scoped_ptr<DictionaryValue> result(new DictionaryValue);
   PrefTransformerInterface* transformer =
diff --git a/chrome/browser/extensions/extension_preference_api.h b/chrome/browser/extensions/extension_preference_api.h
index d020c11a..31c4452 100644
--- a/chrome/browser/extensions/extension_preference_api.h
+++ b/chrome/browser/extensions/extension_preference_api.h
@@ -9,6 +9,35 @@
 #include <string>
 
 #include "chrome/browser/extensions/extension_function.h"
+#include "chrome/browser/prefs/pref_change_registrar.h"
+#include "content/common/notification_observer.h"
+
+class ExtensionPreferenceEventRouter : public NotificationObserver {
+ public:
+  explicit ExtensionPreferenceEventRouter(Profile* profile);
+  virtual ~ExtensionPreferenceEventRouter();
+
+ private:
+  // NotificationObserver implementation.
+  virtual void Observe(NotificationType type,
+                       const NotificationSource& source,
+                       const NotificationDetails& details);
+
+  void OnPrefChanged(PrefService* pref_service, const std::string& pref_key);
+
+  // This method dispatches events to the extension message service.
+  void DispatchEvent(const std::string& extension_id,
+                     const std::string& event_name,
+                     const std::string& json_args);
+
+  PrefChangeRegistrar registrar_;
+  PrefChangeRegistrar incognito_registrar_;
+
+  // Weak, owns us (transitively via ExtensionService).
+  Profile* profile_;
+
+  DISALLOW_COPY_AND_ASSIGN(ExtensionPreferenceEventRouter);
+};
 
 class Value;
 
@@ -36,12 +65,6 @@
   virtual ~GetPreferenceFunction();
   virtual bool RunImpl();
   DECLARE_EXTENSION_FUNCTION_NAME("experimental.preferences.get")
-
- private:
-  // Returns a string constant (defined in API) indicating the level of
-  // control this extension has on the specified preference.
-  const char* GetLevelOfControl(const std::string& browser_pref,
-                                bool incognito) const;
 };
 
 class SetPreferenceFunction : public SyncExtensionFunction {
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index a0d334e..1c751f9 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -34,6 +34,7 @@
 #include "chrome/browser/extensions/extension_history_api.h"
 #include "chrome/browser/extensions/extension_host.h"
 #include "chrome/browser/extensions/extension_management_api.h"
+#include "chrome/browser/extensions/extension_preference_api.h"
 #include "chrome/browser/extensions/extension_process_manager.h"
 #include "chrome/browser/extensions/extension_processes_api.h"
 #include "chrome/browser/extensions/extension_special_storage_policy.h"
@@ -460,6 +461,7 @@
   ExtensionAccessibilityEventRouter::GetInstance()->ObserveProfile(profile_);
   browser_event_router_.reset(new ExtensionBrowserEventRouter(profile_));
   browser_event_router_->Init();
+  preference_event_router_.reset(new ExtensionPreferenceEventRouter(profile_));
   ExtensionBookmarkEventRouter::GetInstance()->Observe(
       profile_->GetBookmarkModel());
   ExtensionCookiesEventRouter::GetInstance()->Init();
@@ -1103,6 +1105,7 @@
     updater_->Stop();
   }
   browser_event_router_.reset();
+  preference_event_router_.reset();
   pref_change_registrar_.RemoveAll();
   profile_ = NULL;
   toolbar_model_.DestroyingProfile();
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index 096319a..5e83300 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -37,6 +37,7 @@
 #include "content/common/property_bag.h"
 
 class ExtensionBrowserEventRouter;
+class ExtensionPreferenceEventRouter;
 class ExtensionServiceBackend;
 class ExtensionToolbarModel;
 class ExtensionUpdater;
@@ -564,6 +565,8 @@
 
   scoped_ptr<ExtensionBrowserEventRouter> browser_event_router_;
 
+  scoped_ptr<ExtensionPreferenceEventRouter> preference_event_router_;
+
   // A collection of external extension providers.  Each provider reads
   // a source of external extension information.  Examples include the
   // windows registry and external_extensions.json.