// 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 <stddef.h>

#include "base/auto_reset.h"
#include "base/command_line.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/content_settings/tab_specific_content_settings.h"
#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
#include "chrome/browser/infobars/infobar_service.h"
#include "chrome/browser/media/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/media_stream_capture_indicator.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/content_settings/content_setting_bubble_model.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/infobars/core/infobar_delegate.h"
#include "components/prefs/pref_service.h"
#include "components/url_formatter/elide_url.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"

using content::WebContentsTester;

class ContentSettingBubbleModelTest : public ChromeRenderViewHostTestHarness {
 protected:
  void SetUp() override {
    ChromeRenderViewHostTestHarness::SetUp();
    TabSpecificContentSettings::CreateForWebContents(web_contents());
    InfoBarService::CreateForWebContents(web_contents());
  }

  void CheckGeolocationBubble(size_t expected_domains,
                              bool expect_clear_link,
                              bool expect_reload_hint) {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        ContentSettingBubbleModel::CreateContentSettingBubbleModel(
            NULL, web_contents(), profile(),
            CONTENT_SETTINGS_TYPE_GEOLOCATION));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    EXPECT_TRUE(bubble_content.title.empty());
    EXPECT_TRUE(bubble_content.radio_group.radio_items.empty());
    EXPECT_TRUE(bubble_content.list_items.empty());
    EXPECT_EQ(expected_domains, bubble_content.domain_lists.size());
    EXPECT_NE(expect_clear_link || expect_reload_hint,
              bubble_content.custom_link.empty());
    EXPECT_EQ(expect_clear_link, bubble_content.custom_link_enabled);
    EXPECT_FALSE(bubble_content.manage_link.empty());
  }

  std::string GetDefaultAudioDevice() {
    PrefService* prefs = profile()->GetPrefs();
    return prefs->GetString(prefs::kDefaultAudioCaptureDevice);
  }

  std::string GetDefaultVideoDevice() {
    PrefService* prefs = profile()->GetPrefs();
    return prefs->GetString(prefs::kDefaultVideoCaptureDevice);
  }
};

TEST_F(ContentSettingBubbleModelTest, ImageRadios) {
  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  content_settings->OnContentBlocked(CONTENT_SETTINGS_TYPE_IMAGES);

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
         NULL, web_contents(), profile(),
         CONTENT_SETTINGS_TYPE_IMAGES));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_FALSE(bubble_content.title.empty());
  EXPECT_EQ(2U, bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(0, bubble_content.radio_group.default_item);
  EXPECT_TRUE(bubble_content.custom_link.empty());
  EXPECT_FALSE(bubble_content.manage_link.empty());
}

TEST_F(ContentSettingBubbleModelTest, Cookies) {
  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  content_settings->OnContentBlocked(CONTENT_SETTINGS_TYPE_COOKIES);

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
          NULL, web_contents(), profile(), CONTENT_SETTINGS_TYPE_COOKIES));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();
  std::string title = bubble_content.title;
  EXPECT_FALSE(title.empty());
  ASSERT_EQ(2U, bubble_content.radio_group.radio_items.size());
  EXPECT_FALSE(bubble_content.custom_link.empty());
  EXPECT_TRUE(bubble_content.custom_link_enabled);
  EXPECT_FALSE(bubble_content.manage_link.empty());

  content_settings->ClearCookieSpecificContentSettings();
  content_settings->OnContentAllowed(CONTENT_SETTINGS_TYPE_COOKIES);
  content_setting_bubble_model.reset(
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
          NULL, web_contents(), profile(), CONTENT_SETTINGS_TYPE_COOKIES));
  const ContentSettingBubbleModel::BubbleContent& bubble_content_2 =
      content_setting_bubble_model->bubble_content();

  EXPECT_FALSE(bubble_content_2.title.empty());
  EXPECT_NE(title, bubble_content_2.title);
  ASSERT_EQ(2U, bubble_content_2.radio_group.radio_items.size());
  EXPECT_EQ(bubble_content_2.radio_group.radio_items[0],
            l10n_util::GetStringUTF8(IDS_ALLOWED_COOKIES_NO_ACTION));
  EXPECT_EQ(bubble_content_2.radio_group.radio_items[1],
            l10n_util::GetStringFUTF8(
                IDS_ALLOWED_COOKIES_BLOCK,
                url_formatter::FormatUrlForSecurityDisplay(
                    web_contents()->GetURL(), profile()->GetPrefs()->GetString(
                                                  prefs::kAcceptLanguages))));
  EXPECT_FALSE(bubble_content_2.custom_link.empty());
  EXPECT_TRUE(bubble_content_2.custom_link_enabled);
  EXPECT_FALSE(bubble_content_2.manage_link.empty());
}

TEST_F(ContentSettingBubbleModelTest, MediastreamMicAndCamera) {
  // Required to break dependency on BrowserMainLoop.
  MediaCaptureDevicesDispatcher::GetInstance()->
      DisableDeviceEnumerationForTesting();

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  std::string request_host = "google.com";
  GURL security_origin("http://" + request_host);
  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
      TabSpecificContentSettings::MICROPHONE_ACCESSED |
      TabSpecificContentSettings::CAMERA_ACCESSED;
  content_settings->OnMediaStreamPermissionSet(security_origin,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               GetDefaultVideoDevice(),
                                               std::string(),
                                               std::string());

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      new ContentSettingMediaStreamBubbleModel(
         nullptr, web_contents(), profile()));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_EQ(bubble_content.title,
            l10n_util::GetStringUTF8(IDS_MICROPHONE_CAMERA_ALLOWED));
  EXPECT_EQ(2U, bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(bubble_content.radio_group.radio_items[0],
            l10n_util::GetStringFUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_NO_ACTION,
                url_formatter::FormatUrlForSecurityDisplay(
                    security_origin, profile()->GetPrefs()->GetString(
                                         prefs::kAcceptLanguages))));
  EXPECT_EQ(bubble_content.radio_group.radio_items[1],
            l10n_util::GetStringUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_BLOCK));
  EXPECT_EQ(0, bubble_content.radio_group.default_item);
  EXPECT_TRUE(bubble_content.custom_link.empty());
  EXPECT_FALSE(bubble_content.custom_link_enabled);
  EXPECT_FALSE(bubble_content.manage_link.empty());
  EXPECT_EQ(2U, bubble_content.media_menus.size());
}

TEST_F(ContentSettingBubbleModelTest, BlockedMediastreamMicAndCamera) {
  // Required to break dependency on BrowserMainLoop.
  MediaCaptureDevicesDispatcher::GetInstance()->
      DisableDeviceEnumerationForTesting();

  WebContentsTester::For(web_contents())->
      NavigateAndCommit(GURL("https://www.example.com"));
  GURL url = web_contents()->GetURL();

  HostContentSettingsMap* host_content_settings_map =
      HostContentSettingsMapFactory::GetForProfile(profile());
  ContentSetting setting = CONTENT_SETTING_BLOCK;
  host_content_settings_map->SetContentSettingDefaultScope(
      url, GURL(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, std::string(),
      setting);
  host_content_settings_map->SetContentSettingDefaultScope(
      url, GURL(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA, std::string(),
      setting);

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
      TabSpecificContentSettings::MICROPHONE_ACCESSED |
      TabSpecificContentSettings::MICROPHONE_BLOCKED |
      TabSpecificContentSettings::CAMERA_ACCESSED |
      TabSpecificContentSettings::CAMERA_BLOCKED;
  content_settings->OnMediaStreamPermissionSet(url,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               GetDefaultVideoDevice(),
                                               std::string(),
                                               std::string());
  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    // Test if the correct radio item is selected for the blocked mediastream
    // setting.
    EXPECT_EQ(1, bubble_content.radio_group.default_item);
  }

  // Test that the media settings where not changed.
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            host_content_settings_map->GetContentSetting(
                url,
                url,
                CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
                std::string()));
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            host_content_settings_map->GetContentSetting(
                url,
                url,
                CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
                std::string()));

  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    // Change the radio setting.
    content_setting_bubble_model->OnRadioClicked(0);
  }
  // Test that the media setting were change correctly.
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            host_content_settings_map->GetContentSetting(
                url,
                url,
                CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
                std::string()));
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            host_content_settings_map->GetContentSetting(
                url,
                url,
                CONTENT_SETTINGS_TYPE_MEDIASTREAM_CAMERA,
                std::string()));
}

// Tests whether a changed setting in the setting bubble is displayed again when
// the bubble is re-opened.
TEST_F(ContentSettingBubbleModelTest, MediastreamContentBubble) {
  // Required to break dependency on BrowserMainLoop.
  MediaCaptureDevicesDispatcher::GetInstance()->
      DisableDeviceEnumerationForTesting();

  WebContentsTester::For(web_contents())->
      NavigateAndCommit(GURL("https://www.example.com"));
  GURL url = web_contents()->GetURL();

  HostContentSettingsMap* host_content_settings_map =
      HostContentSettingsMapFactory::GetForProfile(profile());
  ContentSetting setting = CONTENT_SETTING_BLOCK;
  host_content_settings_map->SetContentSettingDefaultScope(
      url, GURL(), CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC, std::string(),
      setting);

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
      TabSpecificContentSettings::MICROPHONE_ACCESSED |
      TabSpecificContentSettings::MICROPHONE_BLOCKED;
  content_settings->OnMediaStreamPermissionSet(url,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               std::string(),
                                               std::string(),
                                               std::string());
  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    // Test if the correct radio item is selected for the blocked mediastream
    // setting.
    EXPECT_EQ(1, bubble_content.radio_group.default_item);
    // Change the radio setting.
    content_setting_bubble_model->OnRadioClicked(0);
  }
  // Test that the setting was changed.
  EXPECT_EQ(CONTENT_SETTING_ALLOW,
            host_content_settings_map->GetContentSetting(
                url,
                url,
                CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
                std::string()));

  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    // Test that the reload hint is displayed.
    EXPECT_FALSE(bubble_content.custom_link_enabled);
    EXPECT_EQ(bubble_content.custom_link, l10n_util::GetStringUTF8(
              IDS_MEDIASTREAM_SETTING_CHANGED_MESSAGE));

    EXPECT_EQ(0, bubble_content.radio_group.default_item);
    // Restore the radio setting (to block).
    content_setting_bubble_model->OnRadioClicked(1);
  }
  // Test that the media settings were changed again.
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            host_content_settings_map->GetContentSetting(
                url,
                url,
                CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC,
                std::string()));

  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
            nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    // Test that the reload hint is not displayed any more.
    EXPECT_FALSE(bubble_content.custom_link_enabled);
    EXPECT_TRUE(bubble_content.custom_link.empty());

    EXPECT_EQ(1, bubble_content.radio_group.default_item);
  }
}

// Tests whether the media menu settings are correctly persisted in the bubble.
TEST_F(ContentSettingBubbleModelTest, MediastreamContentBubbleMediaMenus) {
  // Required to break dependency on BrowserMainLoop.
  MediaCaptureDevicesDispatcher::GetInstance()->
      DisableDeviceEnumerationForTesting();

  WebContentsTester::For(web_contents())->
      NavigateAndCommit(GURL("https://www.example.com"));
  GURL url = web_contents()->GetURL();

  content::MediaStreamDevices audio_devices;
  content::MediaStreamDevice fake_audio_device1(
      content::MEDIA_DEVICE_AUDIO_CAPTURE, "fake_dev1", "Fake Audio Device 1");
  content::MediaStreamDevice fake_audio_device2(
      content::MEDIA_DEVICE_AUDIO_CAPTURE, "fake_dev2", "Fake Audio Device 2");
  content::MediaStreamDevice fake_audio_device3(
      content::MEDIA_DEVICE_AUDIO_CAPTURE, "fake_dev3", "Fake Audio Device 3");
  audio_devices.push_back(fake_audio_device1);
  audio_devices.push_back(fake_audio_device2);
  audio_devices.push_back(fake_audio_device3);
  MediaCaptureDevicesDispatcher::GetInstance()->SetTestAudioCaptureDevices(
      audio_devices);

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
      TabSpecificContentSettings::MICROPHONE_ACCESSED |
      TabSpecificContentSettings::MICROPHONE_BLOCKED;
  content_settings->OnMediaStreamPermissionSet(url,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               std::string(),
                                               std::string(),
                                               std::string());
  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    EXPECT_TRUE(bubble_content.custom_link.empty());

    EXPECT_EQ(1U, bubble_content.media_menus.size());
    EXPECT_EQ(content::MEDIA_DEVICE_AUDIO_CAPTURE,
              bubble_content.media_menus.begin()->first);
    EXPECT_FALSE(bubble_content.media_menus.begin()->second.disabled);
    // The first audio device should be selected by default.
    EXPECT_TRUE(fake_audio_device1.IsEqual(
                bubble_content.media_menus.begin()->second.selected_device));

    // Select a different (the second) device.
    content_setting_bubble_model->OnMediaMenuClicked(
        content::MEDIA_DEVICE_AUDIO_CAPTURE,
        fake_audio_device2.id);
  }
  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    EXPECT_EQ(1U, bubble_content.media_menus.size());
    EXPECT_EQ(content::MEDIA_DEVICE_AUDIO_CAPTURE,
              bubble_content.media_menus.begin()->first);
    EXPECT_FALSE(bubble_content.media_menus.begin()->second.disabled);
    // The second audio device should be selected.
    EXPECT_TRUE(fake_audio_device2.IsEqual(
                bubble_content.media_menus.begin()->second.selected_device));
    // The "settings changed" message should not be displayed when there is no
    // active capture.
    EXPECT_FALSE(bubble_content.custom_link_enabled);
    EXPECT_TRUE(bubble_content.custom_link.empty());
  }

  // Simulate that an audio stream is being captured.
  scoped_refptr<MediaStreamCaptureIndicator> indicator =
      MediaCaptureDevicesDispatcher::GetInstance()->
        GetMediaStreamCaptureIndicator();
  scoped_ptr<content::MediaStreamUI> media_stream_ui =
      indicator->RegisterMediaStream(web_contents(), audio_devices);
  media_stream_ui->OnStarted(base::Closure());
  microphone_camera_state &= ~TabSpecificContentSettings::MICROPHONE_BLOCKED;
  content_settings->OnMediaStreamPermissionSet(url,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               std::string(),
                                               std::string(),
                                               std::string());

  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    // Settings not changed yet, so the "settings changed" message should not be
    // shown.
    EXPECT_TRUE(bubble_content.custom_link.empty());

    EXPECT_EQ(1U, bubble_content.media_menus.size());
    EXPECT_EQ(content::MEDIA_DEVICE_AUDIO_CAPTURE,
              bubble_content.media_menus.begin()->first);
    EXPECT_FALSE(bubble_content.media_menus.begin()->second.disabled);
    EXPECT_TRUE(fake_audio_device2.IsEqual(
                bubble_content.media_menus.begin()->second.selected_device));

    // Select a different different device.
    content_setting_bubble_model->OnMediaMenuClicked(
        content::MEDIA_DEVICE_AUDIO_CAPTURE,
        fake_audio_device3.id);
  }

  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
            nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    // Test that the reload hint is displayed.
    EXPECT_FALSE(bubble_content.custom_link_enabled);
    EXPECT_EQ(bubble_content.custom_link, l10n_util::GetStringUTF8(
              IDS_MEDIASTREAM_SETTING_CHANGED_MESSAGE));
  }

  // Simulate that yet another audio stream capture request was initiated.
  microphone_camera_state |= TabSpecificContentSettings::MICROPHONE_BLOCKED;
  content_settings->OnMediaStreamPermissionSet(url,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               std::string(),
                                               std::string(),
                                               std::string());

  {
    scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
        new ContentSettingMediaStreamBubbleModel(
           nullptr, web_contents(), profile()));
    const ContentSettingBubbleModel::BubbleContent& bubble_content =
        content_setting_bubble_model->bubble_content();
    // Test that the reload hint is not displayed any more, because this is a
    // new permission request.
    EXPECT_FALSE(bubble_content.custom_link_enabled);
    EXPECT_TRUE(bubble_content.custom_link.empty());

    // Though the audio menu setting should have persisted.
    EXPECT_EQ(1U, bubble_content.media_menus.size());
    EXPECT_EQ(content::MEDIA_DEVICE_AUDIO_CAPTURE,
              bubble_content.media_menus.begin()->first);
    EXPECT_FALSE(bubble_content.media_menus.begin()->second.disabled);
    EXPECT_TRUE(fake_audio_device3.IsEqual(
                bubble_content.media_menus.begin()->second.selected_device));
  }
}

TEST_F(ContentSettingBubbleModelTest, MediastreamMic) {
  // Required to break dependency on BrowserMainLoop.
  MediaCaptureDevicesDispatcher::GetInstance()->
      DisableDeviceEnumerationForTesting();

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  std::string request_host = "google.com";
  GURL security_origin("http://" + request_host);
  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
      TabSpecificContentSettings::MICROPHONE_ACCESSED;
  content_settings->OnMediaStreamPermissionSet(security_origin,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               std::string(),
                                               std::string(),
                                               std::string());

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      new ContentSettingMediaStreamBubbleModel(
          nullptr, web_contents(), profile()));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_EQ(bubble_content.title,
            l10n_util::GetStringUTF8(IDS_MICROPHONE_ACCESSED));
  EXPECT_EQ(2U, bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(bubble_content.radio_group.radio_items[0],
            l10n_util::GetStringFUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_NO_ACTION,
                url_formatter::FormatUrlForSecurityDisplay(
                    security_origin, profile()->GetPrefs()->GetString(
                                         prefs::kAcceptLanguages))));
  EXPECT_EQ(bubble_content.radio_group.radio_items[1],
            l10n_util::GetStringUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_BLOCK));
  EXPECT_EQ(0, bubble_content.radio_group.default_item);
  EXPECT_TRUE(bubble_content.custom_link.empty());
  EXPECT_FALSE(bubble_content.custom_link_enabled);
  EXPECT_FALSE(bubble_content.manage_link.empty());
  EXPECT_EQ(1U, bubble_content.media_menus.size());
  EXPECT_EQ(content::MEDIA_DEVICE_AUDIO_CAPTURE,
            bubble_content.media_menus.begin()->first);

  // Change the microphone access.
  microphone_camera_state |= TabSpecificContentSettings::MICROPHONE_BLOCKED;
  content_settings->OnMediaStreamPermissionSet(security_origin,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               std::string(),
                                               std::string(),
                                               std::string());
  content_setting_bubble_model.reset(
      new ContentSettingMediaStreamBubbleModel(
          nullptr, web_contents(), profile()));
  const ContentSettingBubbleModel::BubbleContent& new_bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_EQ(new_bubble_content.title,
            l10n_util::GetStringUTF8(IDS_MICROPHONE_BLOCKED));
  EXPECT_EQ(2U, new_bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(new_bubble_content.radio_group.radio_items[0],
            l10n_util::GetStringFUTF8(
                IDS_BLOCKED_MEDIASTREAM_MIC_ASK,
                url_formatter::FormatUrlForSecurityDisplay(
                    security_origin, profile()->GetPrefs()->GetString(
                                         prefs::kAcceptLanguages))));
  EXPECT_EQ(new_bubble_content.radio_group.radio_items[1],
            l10n_util::GetStringUTF8(
                IDS_BLOCKED_MEDIASTREAM_MIC_NO_ACTION));
  EXPECT_EQ(1, new_bubble_content.radio_group.default_item);
  EXPECT_TRUE(new_bubble_content.custom_link.empty());
  EXPECT_FALSE(new_bubble_content.custom_link_enabled);
  EXPECT_FALSE(new_bubble_content.manage_link.empty());
  EXPECT_EQ(1U, new_bubble_content.media_menus.size());
  EXPECT_EQ(content::MEDIA_DEVICE_AUDIO_CAPTURE,
            new_bubble_content.media_menus.begin()->first);
}

TEST_F(ContentSettingBubbleModelTest, MediastreamCamera) {
  // Required to break dependency on BrowserMainLoop.
  MediaCaptureDevicesDispatcher::GetInstance()->
      DisableDeviceEnumerationForTesting();

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  std::string request_host = "google.com";
  GURL security_origin("http://" + request_host);
  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
      TabSpecificContentSettings::CAMERA_ACCESSED;
  content_settings->OnMediaStreamPermissionSet(security_origin,
                                               microphone_camera_state,
                                               std::string(),
                                               GetDefaultVideoDevice(),
                                               std::string(),
                                               std::string());

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      new ContentSettingMediaStreamBubbleModel(
          nullptr, web_contents(), profile()));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_EQ(bubble_content.title,
            l10n_util::GetStringUTF8(IDS_CAMERA_ACCESSED));
  EXPECT_EQ(2U, bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(bubble_content.radio_group.radio_items[0],
            l10n_util::GetStringFUTF8(
                IDS_ALLOWED_MEDIASTREAM_CAMERA_NO_ACTION,
                url_formatter::FormatUrlForSecurityDisplay(
                    security_origin, profile()->GetPrefs()->GetString(
                                         prefs::kAcceptLanguages))));
  EXPECT_EQ(bubble_content.radio_group.radio_items[1],
            l10n_util::GetStringUTF8(
                IDS_ALLOWED_MEDIASTREAM_CAMERA_BLOCK));
  EXPECT_EQ(0, bubble_content.radio_group.default_item);
  EXPECT_TRUE(bubble_content.custom_link.empty());
  EXPECT_FALSE(bubble_content.custom_link_enabled);
  EXPECT_FALSE(bubble_content.manage_link.empty());
  EXPECT_EQ(1U, bubble_content.media_menus.size());
  EXPECT_EQ(content::MEDIA_DEVICE_VIDEO_CAPTURE,
            bubble_content.media_menus.begin()->first);

  // Change the camera access.
  microphone_camera_state |= TabSpecificContentSettings::CAMERA_BLOCKED;
  content_settings->OnMediaStreamPermissionSet(security_origin,
                                               microphone_camera_state,
                                               std::string(),
                                               GetDefaultVideoDevice(),
                                               std::string(),
                                               std::string());
  content_setting_bubble_model.reset(
      new ContentSettingMediaStreamBubbleModel(
          nullptr, web_contents(), profile()));
  const ContentSettingBubbleModel::BubbleContent& new_bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_EQ(new_bubble_content.title,
            l10n_util::GetStringUTF8(IDS_CAMERA_BLOCKED));
  EXPECT_EQ(2U, new_bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(new_bubble_content.radio_group.radio_items[0],
            l10n_util::GetStringFUTF8(
                IDS_BLOCKED_MEDIASTREAM_CAMERA_ASK,
                url_formatter::FormatUrlForSecurityDisplay(
                    security_origin, profile()->GetPrefs()->GetString(
                                         prefs::kAcceptLanguages))));
  EXPECT_EQ(new_bubble_content.radio_group.radio_items[1],
            l10n_util::GetStringUTF8(
                IDS_BLOCKED_MEDIASTREAM_CAMERA_NO_ACTION));
  EXPECT_EQ(1, new_bubble_content.radio_group.default_item);
  EXPECT_TRUE(new_bubble_content.custom_link.empty());
  EXPECT_FALSE(new_bubble_content.custom_link_enabled);
  EXPECT_FALSE(new_bubble_content.manage_link.empty());
  EXPECT_EQ(1U, new_bubble_content.media_menus.size());
  EXPECT_EQ(content::MEDIA_DEVICE_VIDEO_CAPTURE,
            new_bubble_content.media_menus.begin()->first);
}

TEST_F(ContentSettingBubbleModelTest, AccumulateMediastreamMicAndCamera) {
  // Required to break dependency on BrowserMainLoop.
  MediaCaptureDevicesDispatcher::GetInstance()->
      DisableDeviceEnumerationForTesting();

  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  std::string request_host = "google.com";
  GURL security_origin("http://" + request_host);

  // Firstly, add microphone access.
  TabSpecificContentSettings::MicrophoneCameraState microphone_camera_state =
      TabSpecificContentSettings::MICROPHONE_ACCESSED;
  content_settings->OnMediaStreamPermissionSet(security_origin,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               std::string(),
                                               std::string(),
                                               std::string());

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      new ContentSettingMediaStreamBubbleModel(
          nullptr, web_contents(), profile()));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_EQ(bubble_content.title,
            l10n_util::GetStringUTF8(IDS_MICROPHONE_ACCESSED));
  EXPECT_EQ(2U, bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(bubble_content.radio_group.radio_items[0],
            l10n_util::GetStringFUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_NO_ACTION,
                url_formatter::FormatUrlForSecurityDisplay(
                    security_origin, profile()->GetPrefs()->GetString(
                                         prefs::kAcceptLanguages))));
  EXPECT_EQ(bubble_content.radio_group.radio_items[1],
            l10n_util::GetStringUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_BLOCK));
  EXPECT_EQ(0, bubble_content.radio_group.default_item);
  EXPECT_EQ(1U, bubble_content.media_menus.size());
  EXPECT_EQ(content::MEDIA_DEVICE_AUDIO_CAPTURE,
            bubble_content.media_menus.begin()->first);

  // Then add camera access.
  microphone_camera_state |= TabSpecificContentSettings::CAMERA_ACCESSED;
  content_settings->OnMediaStreamPermissionSet(security_origin,
                                               microphone_camera_state,
                                               GetDefaultAudioDevice(),
                                               GetDefaultVideoDevice(),
                                               std::string(),
                                               std::string());

  content_setting_bubble_model.reset(
      new ContentSettingMediaStreamBubbleModel(
          nullptr, web_contents(), profile()));
  const ContentSettingBubbleModel::BubbleContent& new_bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_EQ(new_bubble_content.title,
            l10n_util::GetStringUTF8(IDS_MICROPHONE_CAMERA_ALLOWED));
  EXPECT_EQ(2U, new_bubble_content.radio_group.radio_items.size());
  EXPECT_EQ(new_bubble_content.radio_group.radio_items[0],
            l10n_util::GetStringFUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_NO_ACTION,
                url_formatter::FormatUrlForSecurityDisplay(
                    security_origin, profile()->GetPrefs()->GetString(
                                         prefs::kAcceptLanguages))));
  EXPECT_EQ(new_bubble_content.radio_group.radio_items[1],
            l10n_util::GetStringUTF8(
                IDS_ALLOWED_MEDIASTREAM_MIC_AND_CAMERA_BLOCK));
  EXPECT_EQ(0, new_bubble_content.radio_group.default_item);
  EXPECT_EQ(2U, new_bubble_content.media_menus.size());
}

TEST_F(ContentSettingBubbleModelTest, Plugins) {
  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  const base::string16 plugin_name = base::ASCIIToUTF16("plugin_name");

  content_settings->OnContentBlockedWithDetail(CONTENT_SETTINGS_TYPE_PLUGINS,
                                               plugin_name);

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
         NULL, web_contents(), profile(),
         CONTENT_SETTINGS_TYPE_PLUGINS));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();
  EXPECT_FALSE(bubble_content.title.empty());
  ASSERT_EQ(1U, bubble_content.list_items.size());
  EXPECT_EQ(plugin_name,
            base::ASCIIToUTF16(bubble_content.list_items[0].title));
  EXPECT_EQ(2U, bubble_content.radio_group.radio_items.size());
  EXPECT_FALSE(bubble_content.custom_link.empty());
  EXPECT_TRUE(bubble_content.custom_link_enabled);
  EXPECT_FALSE(bubble_content.manage_link.empty());
  EXPECT_FALSE(bubble_content.learn_more_link.empty());
}

TEST_F(ContentSettingBubbleModelTest, PepperBroker) {
  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  content_settings->OnContentBlocked(CONTENT_SETTINGS_TYPE_PPAPI_BROKER);

  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
         NULL, web_contents(), profile(),
         CONTENT_SETTINGS_TYPE_PPAPI_BROKER));
  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model->bubble_content();

  std::string title = bubble_content.title;
  EXPECT_FALSE(title.empty());
  ASSERT_EQ(2U, bubble_content.radio_group.radio_items.size());
  std::string radio1 = bubble_content.radio_group.radio_items[0];
  std::string radio2 = bubble_content.radio_group.radio_items[1];
  EXPECT_FALSE(bubble_content.custom_link_enabled);
  EXPECT_FALSE(bubble_content.manage_link.empty());

  content_settings->ClearBlockedContentSettingsExceptForCookies();
  content_settings->OnContentAllowed(CONTENT_SETTINGS_TYPE_PPAPI_BROKER);
  content_setting_bubble_model.reset(
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
          NULL, web_contents(), profile(),
          CONTENT_SETTINGS_TYPE_PPAPI_BROKER));
  const ContentSettingBubbleModel::BubbleContent& bubble_content_2 =
      content_setting_bubble_model->bubble_content();

  EXPECT_FALSE(bubble_content_2.title.empty());
  EXPECT_NE(title, bubble_content_2.title);
  ASSERT_EQ(2U, bubble_content_2.radio_group.radio_items.size());
  EXPECT_NE(radio1, bubble_content_2.radio_group.radio_items[0]);
  EXPECT_NE(radio2, bubble_content_2.radio_group.radio_items[1]);
  EXPECT_FALSE(bubble_content_2.custom_link_enabled);
  EXPECT_FALSE(bubble_content_2.manage_link.empty());
}

TEST_F(ContentSettingBubbleModelTest, Geolocation) {
  const GURL page_url("http://toplevel.example/");
  const GURL frame1_url("http://host1.example/");
  const GURL frame2_url("http://host2.example:999/");

  NavigateAndCommit(page_url);
  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());

  // One permitted frame, but not in the content map: requires reload.
  content_settings->OnGeolocationPermissionSet(frame1_url, true);
  CheckGeolocationBubble(1, false, true);

  // Add it to the content map, should now have a clear link.
  HostContentSettingsMap* setting_map =
      HostContentSettingsMapFactory::GetForProfile(profile());
  setting_map->SetContentSettingDefaultScope(
      frame1_url, page_url, CONTENT_SETTINGS_TYPE_GEOLOCATION, std::string(),
      CONTENT_SETTING_ALLOW);
  CheckGeolocationBubble(1, true, false);

  // Change the default to allow: no message needed.
  setting_map->SetDefaultContentSetting(CONTENT_SETTINGS_TYPE_GEOLOCATION,
                                         CONTENT_SETTING_ALLOW);
  CheckGeolocationBubble(1, false, false);

  // Second frame denied, but not stored in the content map: requires reload.
  content_settings->OnGeolocationPermissionSet(frame2_url, false);
  CheckGeolocationBubble(2, false, true);

  // Change the default to block: offer a clear link for the persisted frame 1.
  setting_map->SetDefaultContentSetting(CONTENT_SETTINGS_TYPE_GEOLOCATION,
                                         CONTENT_SETTING_BLOCK);
  CheckGeolocationBubble(2, true, false);
}

TEST_F(ContentSettingBubbleModelTest, FileURL) {
  std::string file_url("file:///tmp/test.html");
  NavigateAndCommit(GURL(file_url));
  TabSpecificContentSettings::FromWebContents(web_contents())->OnContentBlocked(
      CONTENT_SETTINGS_TYPE_IMAGES);
  scoped_ptr<ContentSettingBubbleModel> content_setting_bubble_model(
      ContentSettingBubbleModel::CreateContentSettingBubbleModel(
          nullptr, web_contents(), profile(), CONTENT_SETTINGS_TYPE_IMAGES));
  std::string title =
      content_setting_bubble_model->bubble_content().radio_group.radio_items[0];
  ASSERT_NE(std::string::npos, title.find(file_url));
}

TEST_F(ContentSettingBubbleModelTest, RegisterProtocolHandler) {
  const GURL page_url("http://toplevel.example/");
  NavigateAndCommit(page_url);
  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  content_settings->set_pending_protocol_handler(
      ProtocolHandler::CreateProtocolHandler(
          "mailto", GURL("http://www.toplevel.example/")));

  ContentSettingRPHBubbleModel content_setting_bubble_model(
      NULL, web_contents(), profile(), NULL);

  const ContentSettingBubbleModel::BubbleContent& bubble_content =
      content_setting_bubble_model.bubble_content();
  EXPECT_FALSE(bubble_content.title.empty());
  EXPECT_FALSE(bubble_content.radio_group.radio_items.empty());
  EXPECT_TRUE(bubble_content.list_items.empty());
  EXPECT_TRUE(bubble_content.domain_lists.empty());
  EXPECT_TRUE(bubble_content.custom_link.empty());
  EXPECT_FALSE(bubble_content.custom_link_enabled);
  EXPECT_FALSE(bubble_content.manage_link.empty());
}

class FakeDelegate : public ProtocolHandlerRegistry::Delegate {
 public:
  void RegisterExternalHandler(const std::string& protocol) override {
    // Overrides in order to not register the handler with the
    // ChildProcessSecurityPolicy. That has persistent and unalterable
    // side effects on other tests.
  }

  scoped_refptr<shell_integration::DefaultProtocolClientWorker>
  CreateShellWorker(
      const shell_integration::DefaultWebClientWorkerCallback& callback,
      const std::string& protocol) override {
    VLOG(1) << "CreateShellWorker";
    return NULL;
  }

  void RegisterWithOSAsDefaultClient(
      const std::string& protocol,
      ProtocolHandlerRegistry* registry) override {
    VLOG(1) << "Register With OS";
  }
};

TEST_F(ContentSettingBubbleModelTest, RPHAllow) {
  ProtocolHandlerRegistry registry(profile(), new FakeDelegate());
  registry.InitProtocolSettings();

  const GURL page_url("http://toplevel.example/");
  NavigateAndCommit(page_url);
  TabSpecificContentSettings* content_settings =
      TabSpecificContentSettings::FromWebContents(web_contents());
  ProtocolHandler test_handler = ProtocolHandler::CreateProtocolHandler(
      "mailto", GURL("http://www.toplevel.example/"));
  content_settings->set_pending_protocol_handler(test_handler);

  ContentSettingRPHBubbleModel content_setting_bubble_model(
      NULL, web_contents(), profile(), &registry);

  {
    ProtocolHandler handler = registry.GetHandlerFor("mailto");
    EXPECT_TRUE(handler.IsEmpty());
    EXPECT_EQ(CONTENT_SETTING_DEFAULT,
              content_settings->pending_protocol_handler_setting());
  }

  // "0" is the "Allow" radio button.
  content_setting_bubble_model.OnRadioClicked(0);
  {
    ProtocolHandler handler = registry.GetHandlerFor("mailto");
    ASSERT_FALSE(handler.IsEmpty());
    EXPECT_EQ(CONTENT_SETTING_ALLOW,
              content_settings->pending_protocol_handler_setting());
  }

  // "1" is the "Deny" radio button.
  content_setting_bubble_model.OnRadioClicked(1);
  {
    ProtocolHandler handler = registry.GetHandlerFor("mailto");
    EXPECT_TRUE(handler.IsEmpty());
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              content_settings->pending_protocol_handler_setting());
  }

  // "2" is the "Ignore button.
  content_setting_bubble_model.OnRadioClicked(2);
  {
    ProtocolHandler handler = registry.GetHandlerFor("mailto");
    EXPECT_TRUE(handler.IsEmpty());
    EXPECT_EQ(CONTENT_SETTING_DEFAULT,
              content_settings->pending_protocol_handler_setting());
    EXPECT_TRUE(registry.IsIgnored(test_handler));
  }

  // "0" is the "Allow" radio button.
  content_setting_bubble_model.OnRadioClicked(0);
  {
    ProtocolHandler handler = registry.GetHandlerFor("mailto");
    ASSERT_FALSE(handler.IsEmpty());
    EXPECT_EQ(CONTENT_SETTING_ALLOW,
              content_settings->pending_protocol_handler_setting());
    EXPECT_FALSE(registry.IsIgnored(test_handler));
  }

  registry.Shutdown();
}