text: // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/browser/ui/webui/options/content_settings_handler.h"
  
  #include <map>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "base/command_line.h"
  #include "base/prefs/pref_service.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/values.h"
  #include "chrome/browser/browser_process.h"
  #include "chrome/browser/content_settings/content_settings_details.h"
  #include "chrome/browser/content_settings/content_settings_utils.h"
  #include "chrome/browser/content_settings/host_content_settings_map.h"
  #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
  #include "chrome/browser/custom_handlers/protocol_handler_registry_factory.h"
  #include "chrome/browser/extensions/extension_service.h"
  #include "chrome/browser/extensions/extension_special_storage_policy.h"
  #include "chrome/browser/google/google_util.h"
  #include "chrome/browser/notifications/desktop_notification_service.h"
  #include "chrome/browser/notifications/desktop_notification_service_factory.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/ui/browser_list.h"
  #include "chrome/common/chrome_notification_types.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/common/content_settings.h"
  #include "chrome/common/content_settings_pattern.h"
  #include "chrome/common/extensions/extension_set.h"
  #include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
  #include "chrome/common/extensions/permissions/api_permission.h"
  #include "chrome/common/pref_names.h"
  #include "chrome/common/url_constants.h"
  #include "content/public/browser/notification_service.h"
  #include "content/public/browser/notification_source.h"
  #include "content/public/browser/notification_types.h"
  #include "content/public/browser/user_metrics.h"
  #include "content/public/browser/web_ui.h"
  #include "content/public/common/content_switches.h"
  #include "grit/generated_resources.h"
  #include "grit/locale_settings.h"
  #include "ui/base/l10n/l10n_util.h"
  
  #if defined(OS_CHROMEOS)
  #include "chrome/browser/chromeos/login/user_manager.h"
  #endif
  
  using content::UserMetricsAction;
  using extensions::APIPermission;
  
  namespace {
  
  struct ContentSettingsTypeNameEntry {
    ContentSettingsType type;
    const char* name;
  };
  
  // Maps from a secondary pattern to a setting.
  typedef std::map<ContentSettingsPattern, ContentSetting>
      OnePatternSettings;
  // Maps from a primary pattern/source pair to a OnePatternSettings. All the
  // mappings in OnePatternSettings share the given primary pattern and source.
  typedef std::map<std::pair<ContentSettingsPattern, std::string>,
                   OnePatternSettings>
      AllPatternsSettings;
  
  // The AppFilter is used in AddExceptionsGrantedByHostedApps() to choose
  // extensions which should have their extent displayed.
  typedef bool (*AppFilter)(const extensions::Extension& app, Profile* profile);
  
  const char kExceptionsLearnMoreUrl[] =
      "https://support.google.com/chrome/?p=settings_manage_exceptions";
  
  const char* kSetting = "setting";
  const char* kOrigin = "origin";
  const char* kSource = "source";
  const char* kAppName = "appName";
  const char* kAppId = "appId";
  const char* kEmbeddingOrigin = "embeddingOrigin";
  const char* kPreferencesSource = "preference";
  const char* kVideoSetting = "video";
  
  const ContentSettingsTypeNameEntry kContentSettingsTypeGroupNames[] = {
    {CONTENT_SETTINGS_TYPE_COOKIES, "cookies"},
    {CONTENT_SETTINGS_TYPE_IMAGES, "images"},
    {CONTENT_SETTINGS_TYPE_JAVASCRIPT, "javascript"},
    {CONTENT_SETTINGS_TYPE_PLUGINS, "plugins"},
    {CONTENT_SETTINGS_TYPE_POPUPS, "popups"},
    {CONTENT_SETTINGS_TYPE_GEOLOCATION, "location"},
    {CONTENT_SETTINGS_TYPE_NOTIFICATIONS, "notifications"},
    {CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE, "auto-select-certificate"},
    {CONTENT_SETTINGS_TYPE_FULLSCREEN, "fullscreen"},
    {CONTENT_SETTINGS_TYPE_MOUSELOCK, "mouselock"},
    {CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS, "register-protocol-handler"},
    {CONTENT_SETTINGS_TYPE_MEDIASTREAM, "media-stream"},
    {CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, "media-stream-mic"},
    {CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, "media-stream-camera"},
    {CONTENT_SETTINGS_TYPE_PPAPI_BROKER, "ppapi-broker"},
  };
  
  ContentSettingsType ContentSettingsTypeFromGroupName(const std::string& name) {
    for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
      if (name == kContentSettingsTypeGroupNames[i].name)
        return kContentSettingsTypeGroupNames[i].type;
    }
  
    NOTREACHED() << name << " is not a recognized content settings type.";
    return CONTENT_SETTINGS_TYPE_DEFAULT;
  }
  
  std::string ContentSettingToString(ContentSetting setting) {
    switch (setting) {
      case CONTENT_SETTING_ALLOW:
        return "allow";
      case CONTENT_SETTING_ASK:
        return "ask";
      case CONTENT_SETTING_BLOCK:
        return "block";
      case CONTENT_SETTING_SESSION_ONLY:
        return "session";
      case CONTENT_SETTING_DEFAULT:
        return "default";
      case CONTENT_SETTING_NUM_SETTINGS:
        NOTREACHED();
    }
  
    return std::string();
  }
  
  ContentSetting ContentSettingFromString(const std::string& name) {
    if (name == "allow")
      return CONTENT_SETTING_ALLOW;
    if (name == "ask")
      return CONTENT_SETTING_ASK;
    if (name == "block")
      return CONTENT_SETTING_BLOCK;
    if (name == "session")
      return CONTENT_SETTING_SESSION_ONLY;
  
    NOTREACHED() << name << " is not a recognized content setting.";
    return CONTENT_SETTING_DEFAULT;
  }
  
  // Create a DictionaryValue* that will act as a data source for a single row
  // in a HostContentSettingsMap-controlled exceptions table (e.g., cookies).
  // Ownership of the pointer is passed to the caller.
  DictionaryValue* GetExceptionForPage(
      const ContentSettingsPattern& pattern,
      const ContentSettingsPattern& secondary_pattern,
      const ContentSetting& setting,
      const std::string& provider_name) {
    DictionaryValue* exception = new DictionaryValue();
    exception->SetString(kOrigin, pattern.ToString());
    exception->SetString(kEmbeddingOrigin,
                         secondary_pattern == ContentSettingsPattern::Wildcard()
                             ? std::string()
                             : secondary_pattern.ToString());
    exception->SetString(kSetting, ContentSettingToString(setting));
    exception->SetString(kSource, provider_name);
    return exception;
  }
  
  // Create a DictionaryValue* that will act as a data source for a single row
  // in the Geolocation exceptions table. Ownership of the pointer is passed to
  // the caller.
  DictionaryValue* GetGeolocationExceptionForPage(
      const ContentSettingsPattern& origin,
      const ContentSettingsPattern& embedding_origin,
      ContentSetting setting) {
    DictionaryValue* exception = new DictionaryValue();
    exception->SetString(kSetting, ContentSettingToString(setting));
    exception->SetString(kOrigin, origin.ToString());
    exception->SetString(kEmbeddingOrigin, embedding_origin.ToString());
    return exception;
  }
  
  // Create a DictionaryValue* that will act as a data source for a single row
  // in the desktop notifications exceptions table. Ownership of the pointer is
  // passed to the caller.
  DictionaryValue* GetNotificationExceptionForPage(
      const ContentSettingsPattern& pattern,
      ContentSetting setting,
      const std::string& provider_name) {
    DictionaryValue* exception = new DictionaryValue();
    exception->SetString(kSetting, ContentSettingToString(setting));
    exception->SetString(kOrigin, pattern.ToString());
    exception->SetString(kSource, provider_name);
    return exception;
  }
  
  // Returns true whenever the |extension| is hosted and has |permission|.
  // Must have the AppFilter signature.
  template <APIPermission::ID permission>
  bool HostedAppHasPermission(
      const extensions::Extension& extension, Profile* /*profile*/) {
      return extension.is_hosted_app() && extension.HasAPIPermission(permission);
  }
  
  // Add an "Allow"-entry to the list of |exceptions| for a |url_pattern| from
  // the web extent of a hosted |app|.
  void AddExceptionForHostedApp(const std::string& url_pattern,
      const extensions::Extension& app, ListValue* exceptions) {
    DictionaryValue* exception = new DictionaryValue();
    exception->SetString(kSetting, ContentSettingToString(CONTENT_SETTING_ALLOW));
    exception->SetString(kOrigin, url_pattern);
    exception->SetString(kEmbeddingOrigin, url_pattern);
    exception->SetString(kSource, "HostedApp");
    exception->SetString(kAppName, app.name());
    exception->SetString(kAppId, app.id());
    exceptions->Append(exception);
  }
  
  // Asks the |profile| for hosted apps which have the |permission| set, and
  // adds their web extent and launch URL to the |exceptions| list.
  void AddExceptionsGrantedByHostedApps(
      Profile* profile, AppFilter app_filter, ListValue* exceptions) {
    const ExtensionService* extension_service = profile->GetExtensionService();
    // After ExtensionSystem::Init has been called at the browser's start,
    // GetExtensionService() should not return NULL, so this is safe:
    const ExtensionSet* extensions = extension_service->extensions();
  
    for (ExtensionSet::const_iterator extension = extensions->begin();
         extension != extensions->end(); ++extension) {
      if (!app_filter(*extension->get(), profile))
        continue;
  
      extensions::URLPatternSet web_extent = (*extension)->web_extent();
      // Add patterns from web extent.
      for (extensions::URLPatternSet::const_iterator pattern = web_extent.begin();
           pattern != web_extent.end(); ++pattern) {
        std::string url_pattern = pattern->GetAsString();
        AddExceptionForHostedApp(url_pattern, *extension->get(), exceptions);
      }
      // Retrieve the launch URL.
      GURL launch_url =
          extensions::AppLaunchInfo::GetLaunchWebURL(extension->get());
      // Skip adding the launch URL if it is part of the web extent.
      if (web_extent.MatchesURL(launch_url))
        continue;
      AddExceptionForHostedApp(launch_url.spec(), *extension->get(), exceptions);
    }
  }
  
  }  // namespace
  
  namespace options {
  
  ContentSettingsHandler::MediaSettingsInfo::MediaSettingsInfo()
      : flash_default_setting(CONTENT_SETTING_DEFAULT),
        flash_settings_initialized(false),
        last_flash_refresh_request_id(0),
        show_flash_default_link(false),
        show_flash_exceptions_link(false),
        default_setting(CONTENT_SETTING_DEFAULT),
        policy_disable_audio(false),
        policy_disable_video(false),
        default_setting_initialized(false),
        exceptions_initialized(false) {
  }
  
  ContentSettingsHandler::MediaSettingsInfo::~MediaSettingsInfo() {
  }
  
  ContentSettingsHandler::ContentSettingsHandler() {
  }
  
  ContentSettingsHandler::~ContentSettingsHandler() {
  }
  
  void ContentSettingsHandler::GetLocalizedValues(
      DictionaryValue* localized_strings) {
    DCHECK(localized_strings);
  
    static OptionsStringResource resources[] = {
      { "allowException", IDS_EXCEPTIONS_ALLOW_BUTTON },
      { "blockException", IDS_EXCEPTIONS_BLOCK_BUTTON },
      { "sessionException", IDS_EXCEPTIONS_SESSION_ONLY_BUTTON },
      { "askException", IDS_EXCEPTIONS_ASK_BUTTON },
      { "otr_exceptions_explanation", IDS_EXCEPTIONS_OTR_LABEL },
      { "addNewExceptionInstructions", IDS_EXCEPTIONS_ADD_NEW_INSTRUCTIONS },
      { "manageExceptions", IDS_EXCEPTIONS_MANAGE },
      { "manage_handlers", IDS_HANDLERS_MANAGE },
      { "exceptionPatternHeader", IDS_EXCEPTIONS_PATTERN_HEADER },
      { "exceptionBehaviorHeader", IDS_EXCEPTIONS_ACTION_HEADER },
      { "embeddedOnHost", IDS_EXCEPTIONS_GEOLOCATION_EMBEDDED_ON_HOST },
      // Cookies filter.
      { "cookies_tab_label", IDS_COOKIES_TAB_LABEL },
      { "cookies_header", IDS_COOKIES_HEADER },
      { "cookies_allow", IDS_COOKIES_ALLOW_RADIO },
      { "cookies_block", IDS_COOKIES_BLOCK_RADIO },
      { "cookies_session_only", IDS_COOKIES_SESSION_ONLY_RADIO },
      { "cookies_block_3rd_party", IDS_COOKIES_BLOCK_3RDPARTY_CHKBOX },
      { "cookies_clear_when_close", IDS_COOKIES_CLEAR_WHEN_CLOSE_CHKBOX },
      { "cookies_lso_clear_when_close", IDS_COOKIES_LSO_CLEAR_WHEN_CLOSE_CHKBOX },
      { "cookies_show_cookies", IDS_COOKIES_SHOW_COOKIES_BUTTON },
      { "flash_storage_settings", IDS_FLASH_STORAGE_SETTINGS },
      { "flash_storage_url", IDS_FLASH_STORAGE_URL },
  #if defined(ENABLE_GOOGLE_NOW)
      { "googleGeolocationAccessEnable",
         IDS_GEOLOCATION_GOOGLE_ACCESS_ENABLE_CHKBOX },
  #endif
      // Image filter.
      { "images_tab_label", IDS_IMAGES_TAB_LABEL },
      { "images_header", IDS_IMAGES_HEADER },
      { "images_allow", IDS_IMAGES_LOAD_RADIO },
      { "images_block", IDS_IMAGES_NOLOAD_RADIO },
      // JavaScript filter.
      { "javascript_tab_label", IDS_JAVASCRIPT_TAB_LABEL },
      { "javascript_header", IDS_JAVASCRIPT_HEADER },
      { "javascript_allow", IDS_JS_ALLOW_RADIO },
      { "javascript_block", IDS_JS_DONOTALLOW_RADIO },
      // Plug-ins filter.
      { "plugins_tab_label", IDS_PLUGIN_TAB_LABEL },
      { "plugins_header", IDS_PLUGIN_HEADER },
      { "plugins_ask", IDS_PLUGIN_ASK_RADIO },
      { "plugins_allow", IDS_PLUGIN_LOAD_RADIO },
      { "plugins_block", IDS_PLUGIN_NOLOAD_RADIO },
      { "disableIndividualPlugins", IDS_PLUGIN_SELECTIVE_DISABLE },
      // Pop-ups filter.
      { "popups_tab_label", IDS_POPUP_TAB_LABEL },
      { "popups_header", IDS_POPUP_HEADER },
      { "popups_allow", IDS_POPUP_ALLOW_RADIO },
      { "popups_block", IDS_POPUP_BLOCK_RADIO },
      // Location filter.
      { "location_tab_label", IDS_GEOLOCATION_TAB_LABEL },
      { "location_header", IDS_GEOLOCATION_HEADER },
      { "location_allow", IDS_GEOLOCATION_ALLOW_RADIO },
      { "location_ask", IDS_GEOLOCATION_ASK_RADIO },
      { "location_block", IDS_GEOLOCATION_BLOCK_RADIO },
      { "set_by", IDS_GEOLOCATION_SET_BY_HOVER },
      // Notifications filter.
      { "notifications_tab_label", IDS_NOTIFICATIONS_TAB_LABEL },
      { "notifications_header", IDS_NOTIFICATIONS_HEADER },
      { "notifications_allow", IDS_NOTIFICATIONS_ALLOW_RADIO },
      { "notifications_ask", IDS_NOTIFICATIONS_ASK_RADIO },
      { "notifications_block", IDS_NOTIFICATIONS_BLOCK_RADIO },
      // Fullscreen filter.
      { "fullscreen_tab_label", IDS_FULLSCREEN_TAB_LABEL },
      { "fullscreen_header", IDS_FULLSCREEN_HEADER },
      // Mouse Lock filter.
      { "mouselock_tab_label", IDS_MOUSE_LOCK_TAB_LABEL },
      { "mouselock_header", IDS_MOUSE_LOCK_HEADER },
      { "mouselock_allow", IDS_MOUSE_LOCK_ALLOW_RADIO },
      { "mouselock_ask", IDS_MOUSE_LOCK_ASK_RADIO },
      { "mouselock_block", IDS_MOUSE_LOCK_BLOCK_RADIO },
  #if defined(OS_CHROMEOS) || defined(OS_WIN)
      // Protected Content filter
      { "protectedContentTabLabel", IDS_PROTECTED_CONTENT_TAB_LABEL },
      { "protectedContentInfo", IDS_PROTECTED_CONTENT_INFO },
      { "protectedContentEnable", IDS_PROTECTED_CONTENT_ENABLE},
  #endif  // defined(OS_CHROMEOS) || defined(OS_WIN)
      // Media stream capture device filter.
      { "mediaStreamTabLabel", IDS_MEDIA_STREAM_TAB_LABEL },
      { "media-stream_header", IDS_MEDIA_STREAM_HEADER },
      { "mediaStreamAsk", IDS_MEDIA_STREAM_ASK_RADIO },
      { "mediaStreamBlock", IDS_MEDIA_STREAM_BLOCK_RADIO },
      { "mediaStreamAudioAsk", IDS_MEDIA_STREAM_ASK_AUDIO_ONLY_RADIO },
      { "mediaStreamAudioBlock", IDS_MEDIA_STREAM_BLOCK_AUDIO_ONLY_RADIO },
      { "mediaStreamVideoAsk", IDS_MEDIA_STREAM_ASK_VIDEO_ONLY_RADIO },
      { "mediaStreamVideoBlock", IDS_MEDIA_STREAM_BLOCK_VIDEO_ONLY_RADIO },
      { "mediaStreamBubbleAudio", IDS_MEDIA_STREAM_AUDIO_MANAGED },
      { "mediaStreamBubbleVideo", IDS_MEDIA_STREAM_VIDEO_MANAGED },
      { "mediaAudioExceptionHeader", IDS_MEDIA_AUDIO_EXCEPTION_HEADER },
      { "mediaVideoExceptionHeader", IDS_MEDIA_VIDEO_EXCEPTION_HEADER },
      { "mediaPepperFlashDefaultDivergedLabel",
        IDS_MEDIA_PEPPER_FLASH_DEFAULT_DIVERGED_LABEL },
      { "mediaPepperFlashExceptionsDivergedLabel",
        IDS_MEDIA_PEPPER_FLASH_EXCEPTIONS_DIVERGED_LABEL },
      { "mediaPepperFlashChangeLink", IDS_MEDIA_PEPPER_FLASH_CHANGE_LINK },
      { "mediaPepperFlashGlobalPrivacyURL", IDS_FLASH_GLOBAL_PRIVACY_URL },
      { "mediaPepperFlashWebsitePrivacyURL", IDS_FLASH_WEBSITE_PRIVACY_URL },
      // PPAPI broker filter.
      // TODO(bauerb): Use IDS_PPAPI_BROKER_HEADER.
      { "ppapi-broker_header", IDS_PPAPI_BROKER_TAB_LABEL },
      { "ppapiBrokerTabLabel", IDS_PPAPI_BROKER_TAB_LABEL },
      { "ppapi_broker_allow", IDS_PPAPI_BROKER_ALLOW_RADIO },
      { "ppapi_broker_ask", IDS_PPAPI_BROKER_ASK_RADIO },
      { "ppapi_broker_block", IDS_PPAPI_BROKER_BLOCK_RADIO },
    };
  
    RegisterStrings(localized_strings, resources, arraysize(resources));
    RegisterTitle(localized_strings, "contentSettingsPage",
                  IDS_CONTENT_SETTINGS_TITLE);
  
    // Register titles for each of the individual settings whose exception
    // dialogs will be processed by |ContentSettingsHandler|.
    RegisterTitle(localized_strings, "cookies",
                  IDS_COOKIES_TAB_LABEL);
    RegisterTitle(localized_strings, "images",
                  IDS_IMAGES_TAB_LABEL);
    RegisterTitle(localized_strings, "javascript",
                  IDS_JAVASCRIPT_TAB_LABEL);
    RegisterTitle(localized_strings, "plugins",
                  IDS_PLUGIN_TAB_LABEL);
    RegisterTitle(localized_strings, "popups",
                  IDS_POPUP_TAB_LABEL);
    RegisterTitle(localized_strings, "location",
                  IDS_GEOLOCATION_TAB_LABEL);
    RegisterTitle(localized_strings, "notifications",
                  IDS_NOTIFICATIONS_TAB_LABEL);
    RegisterTitle(localized_strings, "fullscreen",
                  IDS_FULLSCREEN_TAB_LABEL);
    RegisterTitle(localized_strings, "mouselock",
                  IDS_MOUSE_LOCK_TAB_LABEL);
    RegisterTitle(localized_strings, "media-stream",
                  IDS_MEDIA_STREAM_TAB_LABEL);
    RegisterTitle(localized_strings, "ppapi-broker",
                  IDS_PPAPI_BROKER_TAB_LABEL);
  
    localized_strings->SetBoolean("newContentSettings",
        CommandLine::ForCurrentProcess()->HasSwitch(switches::kContentSettings2));
    localized_strings->SetString(
        "exceptionsLearnMoreUrl",
        google_util::StringAppendGoogleLocaleParam(
            kExceptionsLearnMoreUrl));
  }
  
  void ContentSettingsHandler::InitializeHandler() {
    notification_registrar_.Add(
        this, chrome::NOTIFICATION_PROFILE_CREATED,
        content::NotificationService::AllSources());
    notification_registrar_.Add(
        this, chrome::NOTIFICATION_PROFILE_DESTROYED,
        content::NotificationService::AllSources());
  
    notification_registrar_.Add(
        this, chrome::NOTIFICATION_CONTENT_SETTINGS_CHANGED,
        content::NotificationService::AllSources());
    notification_registrar_.Add(
        this, chrome::NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED,
        content::NotificationService::AllSources());
    Profile* profile = Profile::FromWebUI(web_ui());
    notification_registrar_.Add(
        this, chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED,
        content::Source<Profile>(profile));
  
    PrefService* prefs = profile->GetPrefs();
    pref_change_registrar_.Init(prefs);
    pref_change_registrar_.Add(
        prefs::kPepperFlashSettingsEnabled,
        base::Bind(&ContentSettingsHandler::OnPepperFlashPrefChanged,
                   base::Unretained(this)));
    pref_change_registrar_.Add(
        prefs::kAudioCaptureAllowed,
        base::Bind(&ContentSettingsHandler::UpdateMediaSettingsView,
                   base::Unretained(this)));
    pref_change_registrar_.Add(
        prefs::kVideoCaptureAllowed,
        base::Bind(&ContentSettingsHandler::UpdateMediaSettingsView,
                   base::Unretained(this)));
  
    flash_settings_manager_.reset(new PepperFlashSettingsManager(this, profile));
  }
  
  void ContentSettingsHandler::InitializePage() {
    media_settings_ = MediaSettingsInfo();
    RefreshFlashMediaSettings();
  
    UpdateHandlersEnabledRadios();
    UpdateAllExceptionsViewsFromModel();
  }
  
  void ContentSettingsHandler::Observe(
      int type,
      const content::NotificationSource& source,
      const content::NotificationDetails& details) {
    switch (type) {
      case chrome::NOTIFICATION_PROFILE_DESTROYED: {
        if (content::Source<Profile>(source).ptr()->IsOffTheRecord()) {
          web_ui()->CallJavascriptFunction(
              "ContentSettingsExceptionsArea.OTRProfileDestroyed");
        }
        break;
      }
  
      case chrome::NOTIFICATION_PROFILE_CREATED: {
        if (content::Source<Profile>(source).ptr()->IsOffTheRecord())
          UpdateAllOTRExceptionsViewsFromModel();
        break;
      }
  
      case chrome::NOTIFICATION_CONTENT_SETTINGS_CHANGED: {
        // Filter out notifications from other profiles.
        HostContentSettingsMap* map =
            content::Source<HostContentSettingsMap>(source).ptr();
        if (map != GetContentSettingsMap() &&
            map != GetOTRContentSettingsMap())
          break;
  
        const ContentSettingsDetails* settings_details =
            content::Details<const ContentSettingsDetails>(details).ptr();
  
        // TODO(estade): we pretend update_all() is always true.
        if (settings_details->update_all_types())
          UpdateAllExceptionsViewsFromModel();
        else
          UpdateExceptionsViewFromModel(settings_details->type());
        break;
      }
  
      case chrome::NOTIFICATION_DESKTOP_NOTIFICATION_SETTINGS_CHANGED: {
        UpdateNotificationExceptionsView();
        break;
      }
  
      case chrome::NOTIFICATION_PROTOCOL_HANDLER_REGISTRY_CHANGED: {
        UpdateHandlersEnabledRadios();
        break;
      }
  
      default:
        OptionsPageUIHandler::Observe(type, source, details);
    }
  }
  
  void ContentSettingsHandler::OnGetPermissionSettingsCompleted(
      uint32 request_id,
      bool success,
      PP_Flash_BrowserOperations_Permission default_permission,
      const ppapi::FlashSiteSettings& sites) {
    if (success && request_id == media_settings_.last_flash_refresh_request_id) {
      media_settings_.flash_settings_initialized = true;
      media_settings_.flash_default_setting =
          PepperFlashContentSettingsUtils::FlashPermissionToContentSetting(
              default_permission);
      PepperFlashContentSettingsUtils::FlashSiteSettingsToMediaExceptions(
          sites, &media_settings_.flash_exceptions);
      PepperFlashContentSettingsUtils::SortMediaExceptions(
          &media_settings_.flash_exceptions);
  
      UpdateFlashMediaLinksVisibility();
    }
  }
  
  void ContentSettingsHandler::UpdateSettingDefaultFromModel(
      ContentSettingsType type) {
    DictionaryValue filter_settings;
    std::string provider_id;
    filter_settings.SetString(ContentSettingsTypeToGroupName(type) + ".value",
                              GetSettingDefaultFromModel(type, &provider_id));
    filter_settings.SetString(
        ContentSettingsTypeToGroupName(type) + ".managedBy", provider_id);
  
    web_ui()->CallJavascriptFunction(
        "ContentSettings.setContentFilterSettingsValue", filter_settings);
  }
  
  void ContentSettingsHandler::UpdateMediaSettingsView() {
    PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
    bool audio_disabled = !prefs->GetBoolean(prefs::kAudioCaptureAllowed) &&
        prefs->IsManagedPreference(prefs::kAudioCaptureAllowed);
    bool video_disabled = !prefs->GetBoolean(prefs::kVideoCaptureAllowed) &&
        prefs->IsManagedPreference(prefs::kVideoCaptureAllowed);
  
    media_settings_.policy_disable_audio = audio_disabled;
    media_settings_.policy_disable_video = video_disabled;
    media_settings_.default_setting =
        GetContentSettingsMap()->GetDefaultContentSetting(
            CONTENT_SETTINGS_TYPE_MEDIASTREAM, NULL);
    media_settings_.default_setting_initialized = true;
    UpdateFlashMediaLinksVisibility();
  
    DictionaryValue media_ui_settings;
    media_ui_settings.SetBoolean("cameraDisabled", video_disabled);
    media_ui_settings.SetBoolean("micDisabled", audio_disabled);
  
    // In case only video is enabled change the text appropriately.
    if (audio_disabled && !video_disabled) {
      media_ui_settings.SetString("askText", "mediaStreamVideoAsk");
      media_ui_settings.SetString("blockText", "mediaStreamVideoBlock");
      media_ui_settings.SetBoolean("showBubble", true);
      media_ui_settings.SetString("bubbleText", "mediaStreamBubbleAudio");
  
      web_ui()->CallJavascriptFunction("ContentSettings.updateMediaUI",
                                       media_ui_settings);
      return;
    }
  
    // In case only audio is enabled change the text appropriately.
    if (video_disabled && !audio_disabled) {
      DictionaryValue media_ui_settings;
      media_ui_settings.SetString("askText", "mediaStreamAudioAsk");
      media_ui_settings.SetString("blockText", "mediaStreamAudioBlock");
      media_ui_settings.SetBoolean("showBubble", true);
      media_ui_settings.SetString("bubbleText", "mediaStreamBubbleVideo");
  
      web_ui()->CallJavascriptFunction("ContentSettings.updateMediaUI",
                                       media_ui_settings);
      return;
    }
  
    if (audio_disabled && video_disabled) {
      // Fake policy controlled default because the user can not change anything
      // until both audio and video are blocked.
      DictionaryValue filter_settings;
      std::string group_name =
          ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_MEDIASTREAM);
      filter_settings.SetString(group_name + ".value",
                                ContentSettingToString(CONTENT_SETTING_BLOCK));
      filter_settings.SetString(group_name + ".managedBy", "policy");
      web_ui()->CallJavascriptFunction(
          "ContentSettings.setContentFilterSettingsValue", filter_settings);
    }
  
    media_ui_settings.SetString("askText", "mediaStreamAsk");
    media_ui_settings.SetString("blockText", "mediaStreamBlock");
    media_ui_settings.SetBoolean("showBubble", false);
    media_ui_settings.SetString("bubbleText", std::string());
  
    web_ui()->CallJavascriptFunction("ContentSettings.updateMediaUI",
                                     media_ui_settings);
  }
  
  std::string ContentSettingsHandler::GetSettingDefaultFromModel(
      ContentSettingsType type, std::string* provider_id) {
    Profile* profile = Profile::FromWebUI(web_ui());
    ContentSetting default_setting;
    if (type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
      default_setting =
          DesktopNotificationServiceFactory::GetForProfile(profile)->
              GetDefaultContentSetting(provider_id);
    } else {
      default_setting =
          profile->GetHostContentSettingsMap()->
              GetDefaultContentSetting(type, provider_id);
    }
  
    return ContentSettingToString(default_setting);
  }
  
  void ContentSettingsHandler::UpdateHandlersEnabledRadios() {
    base::FundamentalValue handlers_enabled(
        GetProtocolHandlerRegistry()->enabled());
  
    web_ui()->CallJavascriptFunction(
        "ContentSettings.updateHandlersEnabledRadios",
        handlers_enabled);
  }
  
  void ContentSettingsHandler::UpdateAllExceptionsViewsFromModel() {
    for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1;
         type < CONTENT_SETTINGS_NUM_TYPES; ++type) {
      UpdateExceptionsViewFromModel(static_cast<ContentSettingsType>(type));
    }
  }
  
  void ContentSettingsHandler::UpdateAllOTRExceptionsViewsFromModel() {
    for (int type = CONTENT_SETTINGS_TYPE_DEFAULT + 1;
         type < CONTENT_SETTINGS_NUM_TYPES; ++type) {
      UpdateOTRExceptionsViewFromModel(static_cast<ContentSettingsType>(type));
    }
  }
  
  void ContentSettingsHandler::UpdateExceptionsViewFromModel(
      ContentSettingsType type) {
    switch (type) {
      case CONTENT_SETTINGS_TYPE_GEOLOCATION:
        UpdateGeolocationExceptionsView();
        break;
      case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
        UpdateNotificationExceptionsView();
        break;
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM:
        UpdateMediaSettingsView();
        break;
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
        UpdateMediaExceptionsView();
        break;
      case CONTENT_SETTINGS_TYPE_MIXEDSCRIPT:
        // We don't yet support exceptions for mixed scripting.
        break;
      case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE:
        // The content settings type CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE
        // is supposed to be set by policy only. Hence there is no user facing UI
        // for this content type and we skip it here.
        break;
      case CONTENT_SETTINGS_TYPE_PROTOCOL_HANDLERS:
        // The RPH settings are retrieved separately.
        break;
  #if defined(OS_WIN)
      case CONTENT_SETTINGS_TYPE_METRO_SWITCH_TO_DESKTOP:
        break;
  #endif
      default:
        UpdateExceptionsViewFromHostContentSettingsMap(type);
        break;
    }
  }
  
  void ContentSettingsHandler::UpdateOTRExceptionsViewFromModel(
      ContentSettingsType type) {
    switch (type) {
      case CONTENT_SETTINGS_TYPE_GEOLOCATION:
      case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
      case CONTENT_SETTINGS_TYPE_AUTO_SELECT_CERTIFICATE:
      case CONTENT_SETTINGS_TYPE_MIXEDSCRIPT:
  #if defined(OS_WIN)
      case CONTENT_SETTINGS_TYPE_METRO_SWITCH_TO_DESKTOP:
  #endif
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM:
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC:
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA:
        break;
      default:
        UpdateExceptionsViewFromOTRHostContentSettingsMap(type);
        break;
    }
  }
  
  // TODO(estade): merge with GetExceptionsFromHostContentSettingsMap.
  void ContentSettingsHandler::UpdateGeolocationExceptionsView() {
    Profile* profile = Profile::FromWebUI(web_ui());
    HostContentSettingsMap* map = profile->GetHostContentSettingsMap();
  
    ContentSettingsForOneType all_settings;
    map->GetSettingsForOneType(
        CONTENT_SETTINGS_TYPE_GEOLOCATION,
        std::string(),
        &all_settings);
  
    // Group geolocation settings by primary_pattern.
    AllPatternsSettings all_patterns_settings;
    for (ContentSettingsForOneType::iterator i = all_settings.begin();
         i != all_settings.end(); ++i) {
      // Don't add default settings.
      if (i->primary_pattern == ContentSettingsPattern::Wildcard() &&
          i->secondary_pattern == ContentSettingsPattern::Wildcard() &&
          i->source != kPreferencesSource) {
        continue;
      }
      all_patterns_settings[std::make_pair(i->primary_pattern, i->source)]
          [i->secondary_pattern] = i->setting;
    }
  
    ListValue exceptions;
    AddExceptionsGrantedByHostedApps(
        profile,
        HostedAppHasPermission<APIPermission::kGeolocation>,
        &exceptions);
  
    for (AllPatternsSettings::iterator i = all_patterns_settings.begin();
         i != all_patterns_settings.end(); ++i) {
      const ContentSettingsPattern& primary_pattern = i->first.first;
      const OnePatternSettings& one_settings = i->second;
  
      OnePatternSettings::const_iterator parent =
          one_settings.find(primary_pattern);
  
      // Add the "parent" entry for the non-embedded setting.
      ContentSetting parent_setting =
          parent == one_settings.end() ? CONTENT_SETTING_DEFAULT : parent->second;
      exceptions.Append(GetGeolocationExceptionForPage(primary_pattern,
                                                       primary_pattern,
                                                       parent_setting));
  
      // Add the "children" for any embedded settings.
      for (OnePatternSettings::const_iterator j = one_settings.begin();
           j != one_settings.end();
           ++j) {
        // Skip the non-embedded setting which we already added above.
        if (j == parent)
          continue;
  
        exceptions.Append(GetGeolocationExceptionForPage(
            primary_pattern, j->first, j->second));
      }
    }
  
    StringValue type_string(
        ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_GEOLOCATION));
    web_ui()->CallJavascriptFunction("ContentSettings.setExceptions",
                                     type_string, exceptions);
  
    // This is mainly here to keep this function ideologically parallel to
    // UpdateExceptionsViewFromHostContentSettingsMap().
    UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_GEOLOCATION);
  }
  
  void ContentSettingsHandler::UpdateNotificationExceptionsView() {
    Profile* profile = Profile::FromWebUI(web_ui());
    DesktopNotificationService* service =
        DesktopNotificationServiceFactory::GetForProfile(profile);
  
    ContentSettingsForOneType settings;
    service->GetNotificationsSettings(&settings);
  
    ListValue exceptions;
    AddExceptionsGrantedByHostedApps(profile,
        HostedAppHasPermission<APIPermission::kNotification>,
        &exceptions);
  
    for (ContentSettingsForOneType::const_iterator i =
             settings.begin();
         i != settings.end();
         ++i) {
      // Don't add default settings.
      if (i->primary_pattern == ContentSettingsPattern::Wildcard() &&
          i->secondary_pattern == ContentSettingsPattern::Wildcard() &&
          i->source != kPreferencesSource) {
        continue;
      }
  
      exceptions.Append(
          GetNotificationExceptionForPage(i->primary_pattern, i->setting,
                                          i->source));
    }
  
    StringValue type_string(
        ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_NOTIFICATIONS));
    web_ui()->CallJavascriptFunction("ContentSettings.setExceptions",
                                     type_string, exceptions);
  
    // This is mainly here to keep this function ideologically parallel to
    // UpdateExceptionsViewFromHostContentSettingsMap().
    UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
  }
  
  void ContentSettingsHandler::UpdateMediaExceptionsView() {
    ListValue media_exceptions;
    GetExceptionsFromHostContentSettingsMap(
        GetContentSettingsMap(),
        CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
        &media_exceptions);
  
    ListValue video_exceptions;
    GetExceptionsFromHostContentSettingsMap(
        GetContentSettingsMap(),
        CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
        &video_exceptions);
  
    // Merge the |video_exceptions| list to |media_exceptions| list.
    std::map<std::string, base::DictionaryValue*> entries_map;
    for (ListValue::const_iterator media_entry(media_exceptions.begin());
         media_entry != media_exceptions.end(); ++media_entry) {
      DictionaryValue* media_dict = NULL;
      if (!(*media_entry)->GetAsDictionary(&media_dict))
        NOTREACHED();
  
      media_dict->SetString(kVideoSetting,
                            ContentSettingToString(CONTENT_SETTING_ASK));
  
      std::string media_origin;
      media_dict->GetString(kOrigin, &media_origin);
      entries_map[media_origin] = media_dict;
    }
  
    for (ListValue::iterator video_entry = video_exceptions.begin();
         video_entry != video_exceptions.end(); ++video_entry) {
      DictionaryValue* video_dict = NULL;
      if (!(*video_entry)->GetAsDictionary(&video_dict))
        NOTREACHED();
  
      std::string video_origin;
      std::string video_setting;
      video_dict->GetString(kOrigin, &video_origin);
      video_dict->GetString(kSetting, &video_setting);
  
      std::map<std::string, base::DictionaryValue*>::iterator iter =
          entries_map.find(video_origin);
      if (iter == entries_map.end()) {
        DictionaryValue* exception = new DictionaryValue();
        exception->SetString(kOrigin, video_origin);
        exception->SetString(kSetting,
                             ContentSettingToString(CONTENT_SETTING_ASK));
        exception->SetString(kVideoSetting, video_setting);
        exception->SetString(kSource, kPreferencesSource);
  
        // Append the new entry to the list and map.
        media_exceptions.Append(exception);
        entries_map[video_origin] = exception;
      } else {
        // Modify the existing entry.
        iter->second->SetString(kVideoSetting, video_setting);
      }
    }
  
    media_settings_.exceptions.clear();
    for (ListValue::const_iterator media_entry = media_exceptions.begin();
         media_entry != media_exceptions.end(); ++media_entry) {
      DictionaryValue* media_dict = NULL;
      bool result = (*media_entry)->GetAsDictionary(&media_dict);
      DCHECK(result);
  
      std::string origin;
      std::string audio_setting;
      std::string video_setting;
      media_dict->GetString(kOrigin, &origin);
      media_dict->GetString(kSetting, &audio_setting);
      media_dict->GetString(kVideoSetting, &video_setting);
      media_settings_.exceptions.push_back(MediaException(
          ContentSettingsPattern::FromString(origin),
          ContentSettingFromString(audio_setting),
          ContentSettingFromString(video_setting)));
    }
    PepperFlashContentSettingsUtils::SortMediaExceptions(
        &media_settings_.exceptions);
    media_settings_.exceptions_initialized = true;
    UpdateFlashMediaLinksVisibility();
  
    StringValue type_string(
         ContentSettingsTypeToGroupName(CONTENT_SETTINGS_TYPE_MEDIASTREAM));
    web_ui()->CallJavascriptFunction("ContentSettings.setExceptions",
                                     type_string, media_exceptions);
  
    UpdateSettingDefaultFromModel(CONTENT_SETTINGS_TYPE_MEDIASTREAM);
  }
  
  void ContentSettingsHandler::UpdateExceptionsViewFromHostContentSettingsMap(
      ContentSettingsType type) {
    ListValue exceptions;
    GetExceptionsFromHostContentSettingsMap(
        GetContentSettingsMap(), type, &exceptions);
    StringValue type_string(ContentSettingsTypeToGroupName(type));
    web_ui()->CallJavascriptFunction("ContentSettings.setExceptions", type_string,
                                     exceptions);
  
    UpdateExceptionsViewFromOTRHostContentSettingsMap(type);
  
    // TODO(koz): The default for fullscreen is always 'ask'.
    // http://crbug.com/104683
    if (type == CONTENT_SETTINGS_TYPE_FULLSCREEN)
      return;
  
    // The default may also have changed (we won't get a separate notification).
    // If it hasn't changed, this call will be harmless.
    UpdateSettingDefaultFromModel(type);
  }
  
  void ContentSettingsHandler::UpdateExceptionsViewFromOTRHostContentSettingsMap(
      ContentSettingsType type) {
    const HostContentSettingsMap* otr_settings_map = GetOTRContentSettingsMap();
    if (!otr_settings_map)
      return;
    ListValue exceptions;
    GetExceptionsFromHostContentSettingsMap(otr_settings_map, type, &exceptions);
    StringValue type_string(ContentSettingsTypeToGroupName(type));
    web_ui()->CallJavascriptFunction("ContentSettings.setOTRExceptions",
                                     type_string, exceptions);
  }
  
  void ContentSettingsHandler::GetExceptionsFromHostContentSettingsMap(
      const HostContentSettingsMap* map,
      ContentSettingsType type,
      ListValue* exceptions) {
    ContentSettingsForOneType entries;
    map->GetSettingsForOneType(type, std::string(), &entries);
    // Group settings by primary_pattern.
    AllPatternsSettings all_patterns_settings;
    for (ContentSettingsForOneType::iterator i = entries.begin();
         i != entries.end(); ++i) {
      // Don't add default settings.
      if (i->primary_pattern == ContentSettingsPattern::Wildcard() &&
          i->secondary_pattern == ContentSettingsPattern::Wildcard() &&
          i->source != kPreferencesSource) {
        continue;
      }
  
      // Off-the-record HostContentSettingsMap contains incognito content settings
      // as well as normal content settings. Here, we use the incongnito settings
      // only.
      if (map->is_off_the_record() && !i->incognito)
        continue;
  
      all_patterns_settings[std::make_pair(i->primary_pattern, i->source)]
          [i->secondary_pattern] = i->setting;
    }
  
    // Keep the exceptions sorted by provider so they will be displayed in
    // precedence order.
    std::vector<std::vector<Value*> > all_provider_exceptions;
    all_provider_exceptions.resize(HostContentSettingsMap::NUM_PROVIDER_TYPES);
  
    for (AllPatternsSettings::iterator i = all_patterns_settings.begin();
         i != all_patterns_settings.end();
         ++i) {
      const ContentSettingsPattern& primary_pattern = i->first.first;
      const OnePatternSettings& one_settings = i->second;
  
      // The "parent" entry either has an identical primary and secondary pattern,
      // or has a wildcard secondary. The two cases are indistinguishable in the
      // UI.
      OnePatternSettings::const_iterator parent =
          one_settings.find(primary_pattern);
      if (parent == one_settings.end())
        parent = one_settings.find(ContentSettingsPattern::Wildcard());
  
      const std::string& source = i->first.second;
      std::vector<Value*>* this_provider_exceptions = &all_provider_exceptions.at(
          HostContentSettingsMap::GetProviderTypeFromSource(source));
  
      // Add the "parent" entry for the non-embedded setting.
      ContentSetting parent_setting =
          parent == one_settings.end() ? CONTENT_SETTING_DEFAULT : parent->second;
      const ContentSettingsPattern& secondary_pattern =
          parent == one_settings.end() ? primary_pattern : parent->first;
      this_provider_exceptions->push_back(GetExceptionForPage(primary_pattern,
                                                              secondary_pattern,
                                                              parent_setting,
                                                              source));
  
      // Add the "children" for any embedded settings.
      for (OnePatternSettings::const_iterator j = one_settings.begin();
           j != one_settings.end(); ++j) {
        // Skip the non-embedded setting which we already added above.
        if (j == parent)
          continue;
  
        ContentSetting content_setting = j->second;
        this_provider_exceptions->push_back(GetExceptionForPage(
            primary_pattern,
            j->first,
            content_setting,
            source));
      }
    }
  
    for (size_t i = 0; i < all_provider_exceptions.size(); ++i) {
      for (size_t j = 0; j < all_provider_exceptions[i].size(); ++j) {
        exceptions->Append(all_provider_exceptions[i][j]);
      }
    }
  }
  
  void ContentSettingsHandler::RemoveNotificationException(
      const ListValue* args, size_t arg_index) {
    Profile* profile = Profile::FromWebUI(web_ui());
    std::string origin;
    std::string setting;
    bool rv = args->GetString(arg_index++, &origin);
    DCHECK(rv);
    rv = args->GetString(arg_index++, &setting);
    DCHECK(rv);
    ContentSetting content_setting = ContentSettingFromString(setting);
  
    DCHECK(content_setting == CONTENT_SETTING_ALLOW ||
           content_setting == CONTENT_SETTING_BLOCK);
    DesktopNotificationServiceFactory::GetForProfile(profile)->
        ClearSetting(ContentSettingsPattern::FromString(origin));
  }
  
  void ContentSettingsHandler::RemoveMediaException(
      const ListValue* args, size_t arg_index) {
    std::string mode;
    bool rv = args->GetString(arg_index++, &mode);
    DCHECK(rv);
  
    std::string pattern;
    rv = args->GetString(arg_index++, &pattern);
    DCHECK(rv);
  
    HostContentSettingsMap* settings_map =
        mode == "normal" ? GetContentSettingsMap() :
                           GetOTRContentSettingsMap();
    if (settings_map) {
      settings_map->SetWebsiteSetting(ContentSettingsPattern::FromString(pattern),
                                      ContentSettingsPattern::Wildcard(),
                                      CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
                                      std::string(),
                                      NULL);
      settings_map->SetWebsiteSetting(ContentSettingsPattern::FromString(pattern),
                                      ContentSettingsPattern::Wildcard(),
                                      CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
                                      std::string(),
                                      NULL);
    }
  }
  
  void ContentSettingsHandler::RemoveExceptionFromHostContentSettingsMap(
      const ListValue* args, size_t arg_index,
      ContentSettingsType type) {
    std::string mode;
    bool rv = args->GetString(arg_index++, &mode);
    DCHECK(rv);
  
    std::string pattern;
    rv = args->GetString(arg_index++, &pattern);
    DCHECK(rv);
  
    std::string secondary_pattern;
    rv = args->GetString(arg_index++, &secondary_pattern);
    DCHECK(rv);
  
    HostContentSettingsMap* settings_map =
        mode == "normal" ? GetContentSettingsMap() :
                           GetOTRContentSettingsMap();
    if (settings_map) {
      settings_map->SetWebsiteSetting(
          ContentSettingsPattern::FromString(pattern),
          secondary_pattern.empty()
              ? ContentSettingsPattern::Wildcard()
              : ContentSettingsPattern::FromString(secondary_pattern),
          type,
          std::string(),
          NULL);
    }
  }
  
  void ContentSettingsHandler::RegisterMessages() {
    web_ui()->RegisterMessageCallback("setContentFilter",
        base::Bind(&ContentSettingsHandler::SetContentFilter,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback("removeException",
        base::Bind(&ContentSettingsHandler::RemoveException,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback("setException",
        base::Bind(&ContentSettingsHandler::SetException,
                   base::Unretained(this)));
    web_ui()->RegisterMessageCallback("checkExceptionPatternValidity",
        base::Bind(&ContentSettingsHandler::CheckExceptionPatternValidity,
                   base::Unretained(this)));
  }
  
  void ContentSettingsHandler::ApplyWhitelist(ContentSettingsType content_type,
                                              ContentSetting default_setting) {
    Profile* profile = Profile::FromWebUI(web_ui());
    HostContentSettingsMap* map = GetContentSettingsMap();
    if (content_type != CONTENT_SETTINGS_TYPE_PLUGINS)
      return;
    const int kDefaultWhitelistVersion = 1;
    PrefService* prefs = profile->GetPrefs();
    int version = prefs->GetInteger(
        prefs::kContentSettingsDefaultWhitelistVersion);
    if (version >= kDefaultWhitelistVersion)
      return;
    ContentSetting old_setting =
        map->GetDefaultContentSetting(CONTENT_SETTINGS_TYPE_PLUGINS, NULL);
    // TODO(bauerb): Remove this once the Google Talk plug-in works nicely with
    // click-to-play (b/6090625).
    if (old_setting == CONTENT_SETTING_ALLOW &&
        default_setting == CONTENT_SETTING_ASK) {
      map->SetWebsiteSetting(
          ContentSettingsPattern::Wildcard(),
          ContentSettingsPattern::Wildcard(),
          CONTENT_SETTINGS_TYPE_PLUGINS,
          "google-talk",
          new base::FundamentalValue(CONTENT_SETTING_ALLOW));
    }
    prefs->SetInteger(prefs::kContentSettingsDefaultWhitelistVersion,
                      kDefaultWhitelistVersion);
  }
  
  void ContentSettingsHandler::SetContentFilter(const ListValue* args) {
    DCHECK_EQ(2U, args->GetSize());
    std::string group, setting;
    if (!(args->GetString(0, &group) &&
          args->GetString(1, &setting))) {
      NOTREACHED();
      return;
    }
  
    ContentSetting default_setting = ContentSettingFromString(setting);
    ContentSettingsType content_type = ContentSettingsTypeFromGroupName(group);
    Profile* profile = Profile::FromWebUI(web_ui());
  
  #if defined(OS_CHROMEOS)
    // ChromeOS special case : in Guest mode settings are opened in Incognito
    // mode, so we need original profile to actually modify settings.
    if (chromeos::UserManager::Get()->IsLoggedInAsGuest())
      profile = profile->GetOriginalProfile();
  #endif
  
    if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
      DesktopNotificationServiceFactory::GetForProfile(profile)->
          SetDefaultContentSetting(default_setting);
    } else {
      HostContentSettingsMap* map = profile->GetHostContentSettingsMap();
      ApplyWhitelist(content_type, default_setting);
      map->SetDefaultContentSetting(content_type, default_setting);
    }
    switch (content_type) {
      case CONTENT_SETTINGS_TYPE_COOKIES:
        content::RecordAction(
            UserMetricsAction("Options_DefaultCookieSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_IMAGES:
        content::RecordAction(
            UserMetricsAction("Options_DefaultImagesSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_JAVASCRIPT:
        content::RecordAction(
            UserMetricsAction("Options_DefaultJavaScriptSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_PLUGINS:
        content::RecordAction(
            UserMetricsAction("Options_DefaultPluginsSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_POPUPS:
        content::RecordAction(
            UserMetricsAction("Options_DefaultPopupsSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
        content::RecordAction(
            UserMetricsAction("Options_DefaultNotificationsSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_GEOLOCATION:
        content::RecordAction(
            UserMetricsAction("Options_DefaultGeolocationSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_MOUSELOCK:
        content::RecordAction(
            UserMetricsAction("Options_DefaultMouseLockSettingChanged"));
        break;
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM:
        content::RecordAction(
            UserMetricsAction("Options_DefaultMediaStreamMicSettingChanged"));
        break;
      default:
        break;
    }
  }
  
  void ContentSettingsHandler::RemoveException(const ListValue* args) {
    size_t arg_i = 0;
    std::string type_string;
    CHECK(args->GetString(arg_i++, &type_string));
  
    ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string);
    switch (type) {
      case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
        RemoveNotificationException(args, arg_i);
        break;
      case CONTENT_SETTINGS_TYPE_MEDIASTREAM:
        RemoveMediaException(args, arg_i);
        break;
      default:
        RemoveExceptionFromHostContentSettingsMap(args, arg_i, type);
        break;
    }
  }
  
  void ContentSettingsHandler::SetException(const ListValue* args) {
    size_t arg_i = 0;
    std::string type_string;
    CHECK(args->GetString(arg_i++, &type_string));
    std::string mode;
    CHECK(args->GetString(arg_i++, &mode));
    std::string pattern;
    CHECK(args->GetString(arg_i++, &pattern));
    std::string setting;
    CHECK(args->GetString(arg_i++, &setting));
  
    ContentSettingsType type = ContentSettingsTypeFromGroupName(type_string);
    if (type == CONTENT_SETTINGS_TYPE_GEOLOCATION ||
        type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS ||
        type == CONTENT_SETTINGS_TYPE_MEDIASTREAM ||
        type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC ||
        type == CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA) {
      NOTREACHED();
    } else {
      HostContentSettingsMap* settings_map =
          mode == "normal" ? GetContentSettingsMap() :
                             GetOTRContentSettingsMap();
  
      // The settings map could be null if the mode was OTR but the OTR profile
      // got destroyed before we received this message.
      if (!settings_map)
        return;
      settings_map->SetContentSetting(ContentSettingsPattern::FromString(pattern),
                                      ContentSettingsPattern::Wildcard(),
                                      type,
                                      std::string(),
                                      ContentSettingFromString(setting));
    }
  }
  
  void ContentSettingsHandler::CheckExceptionPatternValidity(
      const ListValue* args) {
    size_t arg_i = 0;
    std::string type_string;
    CHECK(args->GetString(arg_i++, &type_string));
    std::string mode_string;
    CHECK(args->GetString(arg_i++, &mode_string));
    std::string pattern_string;
    CHECK(args->GetString(arg_i++, &pattern_string));
  
    ContentSettingsPattern pattern =
        ContentSettingsPattern::FromString(pattern_string);
  
    web_ui()->CallJavascriptFunction(
        "ContentSettings.patternValidityCheckComplete",
        base::StringValue(type_string),
        base::StringValue(mode_string),
        base::StringValue(pattern_string),
        base::FundamentalValue(pattern.IsValid()));
  }
  
  // static
  std::string ContentSettingsHandler::ContentSettingsTypeToGroupName(
      ContentSettingsType type) {
    for (size_t i = 0; i < arraysize(kContentSettingsTypeGroupNames); ++i) {
      if (type == kContentSettingsTypeGroupNames[i].type)
        return kContentSettingsTypeGroupNames[i].name;
    }
  
    NOTREACHED();
    return std::string();
  }
  
  HostContentSettingsMap* ContentSettingsHandler::GetContentSettingsMap() {
    return Profile::FromWebUI(web_ui())->GetHostContentSettingsMap();
  }
  
  ProtocolHandlerRegistry* ContentSettingsHandler::GetProtocolHandlerRegistry() {
    return ProtocolHandlerRegistryFactory::GetForProfile(
        Profile::FromWebUI(web_ui()));
  }
  
  HostContentSettingsMap*
      ContentSettingsHandler::GetOTRContentSettingsMap() {
    Profile* profile = Profile::FromWebUI(web_ui());
    if (profile->HasOffTheRecordProfile())
      return profile->GetOffTheRecordProfile()->GetHostContentSettingsMap();
    return NULL;
  }
  
  void ContentSettingsHandler::RefreshFlashMediaSettings() {
    media_settings_.flash_settings_initialized = false;
  
    media_settings_.last_flash_refresh_request_id =
        flash_settings_manager_->GetPermissionSettings(
            PP_FLASH_BROWSEROPERATIONS_SETTINGTYPE_CAMERAMIC);
  }
  
  void ContentSettingsHandler::OnPepperFlashPrefChanged() {
    ShowFlashMediaLink(DEFAULT_SETTING, false);
    ShowFlashMediaLink(EXCEPTIONS, false);
  
    PrefService* prefs = Profile::FromWebUI(web_ui())->GetPrefs();
    if (prefs->GetBoolean(prefs::kPepperFlashSettingsEnabled))
      RefreshFlashMediaSettings();
    else
      media_settings_.flash_settings_initialized = false;
  }
  
  void ContentSettingsHandler::ShowFlashMediaLink(LinkType link_type, bool show) {
    bool& show_link = link_type == DEFAULT_SETTING ?
        media_settings_.show_flash_default_link :
        media_settings_.show_flash_exceptions_link;
    if (show_link != show) {
      web_ui()->CallJavascriptFunction(
          link_type == DEFAULT_SETTING ?
              "ContentSettings.showMediaPepperFlashDefaultLink" :
              "ContentSettings.showMediaPepperFlashExceptionsLink",
          base::FundamentalValue(show));
      show_link = show;
    }
  }
  
  void ContentSettingsHandler::UpdateFlashMediaLinksVisibility() {
    if (!media_settings_.flash_settings_initialized ||
        !media_settings_.default_setting_initialized ||
        !media_settings_.exceptions_initialized) {
      return;
    }
  
    // Flash won't send us notifications when its settings get changed, which
    // means the Flash settings in |media_settings_| may be out-dated, especially
    // after we show links to change Flash settings.
    // In order to avoid confusion, we won't hide the links once they are showed.
    // One exception is that we will hide them when Pepper Flash is disabled
    // (handled in OnPepperFlashPrefChanged()).
    if (media_settings_.show_flash_default_link &&
        media_settings_.show_flash_exceptions_link) {
      return;
    }
  
    if (!media_settings_.show_flash_default_link) {
      // If both audio and video capture are disabled by policy, the link
      // shouldn't be showed. Flash conforms to the policy in this case because
      // it cannot open those devices. We don't have to look at the Flash
      // settings.
      if (!(media_settings_.policy_disable_audio &&
            media_settings_.policy_disable_video) &&
          media_settings_.flash_default_setting !=
              media_settings_.default_setting) {
        ShowFlashMediaLink(DEFAULT_SETTING, true);
      }
    }
    if (!media_settings_.show_flash_exceptions_link) {
      // If audio or video capture is disabled by policy, we skip comparison of
      // exceptions for audio or video capture, respectively.
      if (!PepperFlashContentSettingsUtils::AreMediaExceptionsEqual(
              media_settings_.default_setting,
              media_settings_.exceptions,
              media_settings_.flash_default_setting,
              media_settings_.flash_exceptions,
              media_settings_.policy_disable_audio,
              media_settings_.policy_disable_video)) {
        ShowFlashMediaLink(EXCEPTIONS, true);
      }
    }
  }
  
  }  // namespace options
  