text: // Copyright (c) 2011 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/common/extensions/extension.h"
  
  #if defined(TOOLKIT_GTK)
  #include <gtk/gtk.h>
  #endif
  
  #include "base/format_macros.h"
  #include "base/file_path.h"
  #include "base/file_util.h"
  #include "base/i18n/rtl.h"
  #include "base/path_service.h"
  #include "base/string_number_conversions.h"
  #include "base/string_util.h"
  #include "base/utf_string_conversions.h"
  #include "chrome/common/chrome_paths.h"
  #include "chrome/common/extensions/extension_action.h"
  #include "chrome/common/extensions/extension_constants.h"
  #include "chrome/common/extensions/extension_error_utils.h"
  #include "chrome/common/extensions/extension_resource.h"
  #include "chrome/common/url_constants.h"
  #include "content/common/json_value_serializer.h"
  #include "googleurl/src/gurl.h"
  #include "net/base/mime_sniffer.h"
  #include "skia/ext/image_operations.h"
  #include "chrome/test/ui_test_utils.h"
  #include "net/base/mock_host_resolver.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/skia/include/core/SkBitmap.h"
  #include "ui/base/l10n/l10n_util.h"
  #include "ui/gfx/codec/png_codec.h"
  
  namespace keys = extension_manifest_keys;
  namespace values = extension_manifest_values;
  namespace errors = extension_manifest_errors;
  
  namespace {
  
  void CompareLists(const std::vector<std::string>& expected,
                    const std::vector<std::string>& actual) {
    ASSERT_EQ(expected.size(), actual.size());
  
    for (size_t i = 0; i < expected.size(); ++i) {
      EXPECT_EQ(expected[i], actual[i]);
    }
  }
  
  static void AddPattern(ExtensionExtent* extent, const std::string& pattern) {
    int schemes = URLPattern::SCHEME_ALL;
    extent->AddPattern(URLPattern(schemes, pattern));
  }
  
  }
  
  class ExtensionTest : public testing::Test {
  };
  
  // We persist location values in the preferences, so this is a sanity test that
  // someone doesn't accidentally change them.
  TEST(ExtensionTest, LocationValuesTest) {
    ASSERT_EQ(0, Extension::INVALID);
    ASSERT_EQ(1, Extension::INTERNAL);
    ASSERT_EQ(2, Extension::EXTERNAL_PREF);
    ASSERT_EQ(3, Extension::EXTERNAL_REGISTRY);
    ASSERT_EQ(4, Extension::LOAD);
    ASSERT_EQ(5, Extension::COMPONENT);
    ASSERT_EQ(6, Extension::EXTERNAL_PREF_DOWNLOAD);
    ASSERT_EQ(7, Extension::EXTERNAL_POLICY_DOWNLOAD);
  }
  
  TEST(ExtensionTest, LocationPriorityTest) {
    for (int i = 0; i < Extension::NUM_LOCATIONS; i++) {
      Extension::Location loc = static_cast<Extension::Location>(i);
  
      // INVALID is not a valid location.
      if (loc == Extension::INVALID)
        continue;
  
      // Comparing a location that has no rank will hit a CHECK. Do a
      // compare with every valid location, to be sure each one is covered.
  
      // Check that no install source can override a componenet extension.
      ASSERT_EQ(Extension::COMPONENT,
                Extension::GetHigherPriorityLocation(Extension::COMPONENT, loc));
      ASSERT_EQ(Extension::COMPONENT,
                Extension::GetHigherPriorityLocation(loc, Extension::COMPONENT));
  
      // Check that any source can override a user install. This might change
      // in the future, in which case this test should be updated.
      ASSERT_EQ(loc,
                Extension::GetHigherPriorityLocation(Extension::INTERNAL, loc));
      ASSERT_EQ(loc,
                Extension::GetHigherPriorityLocation(loc, Extension::INTERNAL));
    }
  
    // Check a few interesting cases that we know can happen:
    ASSERT_EQ(Extension::EXTERNAL_POLICY_DOWNLOAD,
              Extension::GetHigherPriorityLocation(
                  Extension::EXTERNAL_POLICY_DOWNLOAD,
                  Extension::EXTERNAL_PREF));
  
    ASSERT_EQ(Extension::EXTERNAL_PREF,
              Extension::GetHigherPriorityLocation(
                  Extension::INTERNAL,
                  Extension::EXTERNAL_PREF));
  }
  
  
  // Please don't put any more manifest tests here!!
  // Move them to extension_manifest_unittest.cc instead and make them use the
  // more data-driven style there instead.
  // Bug: http://crbug.com/38462
  
  
  TEST(ExtensionTest, InitFromValueInvalid) {
  #if defined(OS_WIN)
    FilePath path(FILE_PATH_LITERAL("c:\\foo"));
  #elif defined(OS_POSIX)
    FilePath path(FILE_PATH_LITERAL("/foo"));
  #endif
    scoped_refptr<Extension> extension_ptr(new Extension(path,
                                                         Extension::INVALID));
    Extension& extension = *extension_ptr;
    int error_code = 0;
    std::string error;
  
    // Start with a valid extension manifest
    FilePath extensions_path;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &extensions_path));
    extensions_path = extensions_path.AppendASCII("extensions")
        .AppendASCII("good")
        .AppendASCII("Extensions")
        .AppendASCII("behllobkkfkfnphdnhnkndlbkcpglgmj")
        .AppendASCII("1.0.0.0")
        .Append(Extension::kManifestFilename);
  
    JSONFileValueSerializer serializer(extensions_path);
    scoped_ptr<DictionaryValue> valid_value(
        static_cast<DictionaryValue*>(serializer.Deserialize(&error_code,
                                                             &error)));
    EXPECT_EQ("", error);
    EXPECT_EQ(0, error_code);
    ASSERT_TRUE(valid_value.get());
    ASSERT_TRUE(extension.InitFromValue(*valid_value, Extension::REQUIRE_KEY,
                                        &error));
    ASSERT_EQ("", error);
    EXPECT_EQ("en_US", extension.default_locale());
  
    scoped_ptr<DictionaryValue> input_value;
  
    // Test missing and invalid versions
    input_value.reset(valid_value->DeepCopy());
    input_value->Remove(keys::kVersion, NULL);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_EQ(errors::kInvalidVersion, error);
  
    input_value->SetInteger(keys::kVersion, 42);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_EQ(errors::kInvalidVersion, error);
  
    // Test missing and invalid names.
    input_value.reset(valid_value->DeepCopy());
    input_value->Remove(keys::kName, NULL);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_EQ(errors::kInvalidName, error);
  
    input_value->SetInteger(keys::kName, 42);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_EQ(errors::kInvalidName, error);
  
    // Test invalid description
    input_value.reset(valid_value->DeepCopy());
    input_value->SetInteger(keys::kDescription, 42);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_EQ(errors::kInvalidDescription, error);
  
    // Test invalid icons
    input_value.reset(valid_value->DeepCopy());
    input_value->SetInteger(keys::kIcons, 42);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_EQ(errors::kInvalidIcons, error);
  
    // Test invalid icon paths
    input_value.reset(valid_value->DeepCopy());
    DictionaryValue* icons = NULL;
    input_value->GetDictionary(keys::kIcons, &icons);
    ASSERT_FALSE(NULL == icons);
    icons->SetInteger(base::IntToString(128), 42);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidIconPath));
  
    // Test invalid user scripts list
    input_value.reset(valid_value->DeepCopy());
    input_value->SetInteger(keys::kContentScripts, 42);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_EQ(errors::kInvalidContentScriptsList, error);
  
    // Test invalid user script item
    input_value.reset(valid_value->DeepCopy());
    ListValue* content_scripts = NULL;
    input_value->GetList(keys::kContentScripts, &content_scripts);
    ASSERT_FALSE(NULL == content_scripts);
    content_scripts->Set(0, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidContentScript));
  
    // Test missing and invalid matches array
    input_value.reset(valid_value->DeepCopy());
    input_value->GetList(keys::kContentScripts, &content_scripts);
    DictionaryValue* user_script = NULL;
    content_scripts->GetDictionary(0, &user_script);
    user_script->Remove(keys::kMatches, NULL);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatches));
  
    user_script->Set(keys::kMatches, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatches));
  
    ListValue* matches = new ListValue;
    user_script->Set(keys::kMatches, matches);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatchCount));
  
    // Test invalid match element
    matches->Set(0, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatch));
  
    matches->Set(0, Value::CreateStringValue("chrome://*/*"));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidMatch));
  
    // Test missing and invalid files array
    input_value.reset(valid_value->DeepCopy());
    input_value->GetList(keys::kContentScripts, &content_scripts);
    content_scripts->GetDictionary(0, &user_script);
    user_script->Remove(keys::kJs, NULL);
    user_script->Remove(keys::kCss, NULL);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kMissingFile));
  
    user_script->Set(keys::kJs, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidJsList));
  
    user_script->Set(keys::kCss, new ListValue);
    user_script->Set(keys::kJs, new ListValue);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kMissingFile));
    user_script->Remove(keys::kCss, NULL);
  
    ListValue* files = new ListValue;
    user_script->Set(keys::kJs, files);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kMissingFile));
  
    // Test invalid file element
    files->Set(0, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidJs));
  
    user_script->Remove(keys::kJs, NULL);
    // Test the css element
    user_script->Set(keys::kCss, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidCssList));
  
    // Test invalid file element
    ListValue* css_files = new ListValue;
    user_script->Set(keys::kCss, css_files);
    css_files->Set(0, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidCss));
  
    // Test missing and invalid permissions array
    input_value.reset(valid_value->DeepCopy());
    EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                        &error));
  
    ListValue* permissions = NULL;
    input_value->GetList(keys::kPermissions, &permissions);
    ASSERT_FALSE(NULL == permissions);
  
    permissions = new ListValue;
    input_value->Set(keys::kPermissions, permissions);
    EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                        &error));
  
    input_value->Set(keys::kPermissions, Value::CreateIntegerValue(9));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermissions));
  
    input_value.reset(valid_value->DeepCopy());
    input_value->GetList(keys::kPermissions, &permissions);
    permissions->Set(0, Value::CreateIntegerValue(24));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermission));
  
    // We allow unknown API permissions, so this will be valid until we better
    // distinguish between API and host permissions.
    permissions->Set(0, Value::CreateStringValue("www.google.com"));
    EXPECT_TRUE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                        &error));
  
    // Multiple page actions are not allowed.
    input_value.reset(valid_value->DeepCopy());
    DictionaryValue* action = new DictionaryValue;
    action->SetString(keys::kPageActionId, "MyExtensionActionId");
    action->SetString(keys::kName, "MyExtensionActionName");
    ListValue* action_list = new ListValue;
    action_list->Append(action->DeepCopy());
    action_list->Append(action);
    input_value->Set(keys::kPageActions, action_list);
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_STREQ(errors::kInvalidPageActionsListSize, error.c_str());
  
    // Test invalid options page url.
    input_value.reset(valid_value->DeepCopy());
    input_value->Set(keys::kOptionsPage, Value::CreateNullValue());
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidOptionsPage));
  
    // Test invalid/empty default locale.
    input_value.reset(valid_value->DeepCopy());
    input_value->Set(keys::kDefaultLocale, Value::CreateIntegerValue(5));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale));
  
    input_value->Set(keys::kDefaultLocale, Value::CreateStringValue(""));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidDefaultLocale));
  
    // Test invalid minimum_chrome_version.
    input_value.reset(valid_value->DeepCopy());
    input_value->Set(keys::kMinimumChromeVersion, Value::CreateIntegerValue(42));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kInvalidMinimumChromeVersion));
  
  #if !defined(OS_MACOSX)
    // TODO(aa): The version isn't stamped into the unit test binary on mac.
    input_value->Set(keys::kMinimumChromeVersion,
                     Value::CreateStringValue("88.8"));
    EXPECT_FALSE(extension.InitFromValue(*input_value, Extension::REQUIRE_KEY,
                                         &error));
    EXPECT_TRUE(MatchPattern(error, errors::kChromeVersionTooLow));
  #endif
  }
  
  TEST(ExtensionTest, InitFromValueValid) {
  #if defined(OS_WIN)
    FilePath path(FILE_PATH_LITERAL("C:\\foo"));
  #elif defined(OS_POSIX)
    FilePath path(FILE_PATH_LITERAL("/foo"));
  #endif
    scoped_refptr<Extension> extension_ptr(new Extension(path,
                                                         Extension::INVALID));
    Extension& extension = *extension_ptr;
    std::string error;
    DictionaryValue input_value;
  
    // Test minimal extension
    input_value.SetString(keys::kVersion, "1.0.0.0");
    input_value.SetString(keys::kName, "my extension");
  
    EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
                                        &error));
    EXPECT_EQ("", error);
    EXPECT_TRUE(Extension::IdIsValid(extension.id()));
    EXPECT_EQ("1.0.0.0", extension.VersionString());
    EXPECT_EQ("my extension", extension.name());
    EXPECT_EQ(extension.id(), extension.url().host());
    EXPECT_EQ(path.value(), extension.path().value());
  
    // Test permissions scheme.
    ListValue* permissions = new ListValue;
    permissions->Set(0, Value::CreateStringValue("file:///C:/foo.txt"));
    input_value.Set(keys::kPermissions, permissions);
  
    // We allow unknown API permissions, so this will be valid until we better
    // distinguish between API and host permissions.
    EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
                                        &error));
    input_value.Remove(keys::kPermissions, NULL);
  
    // Test with an options page.
    input_value.SetString(keys::kOptionsPage, "options.html");
    EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
                                        &error));
    EXPECT_EQ("", error);
    EXPECT_EQ("chrome-extension", extension.options_url().scheme());
    EXPECT_EQ("/options.html", extension.options_url().path());
  
    // Test that an empty list of page actions does not stop a browser action
    // from being loaded.
    ListValue* empty_list = new ListValue;
    input_value.Set(keys::kPageActions, empty_list);
    EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
                                        &error));
    EXPECT_EQ("", error);
  
  #if !defined(OS_MACOSX)
    // TODO(aa): The version isn't stamped into the unit test binary on mac.
    // Test with a minimum_chrome_version.
    input_value.SetString(keys::kMinimumChromeVersion, "1.0");
    EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
                                        &error));
    EXPECT_EQ("", error);
    // The minimum chrome version is not stored in the Extension object.
  #endif
  }
  
  TEST(ExtensionTest, InitFromValueValidNameInRTL) {
  #if defined(TOOLKIT_GTK)
    GtkTextDirection gtk_dir = gtk_widget_get_default_direction();
    gtk_widget_set_default_direction(GTK_TEXT_DIR_RTL);
  #else
    std::string locale = l10n_util::GetApplicationLocale("");
    base::i18n::SetICUDefaultLocale("he");
  #endif
  
  #if defined(OS_WIN)
    FilePath path(FILE_PATH_LITERAL("C:\\foo"));
  #elif defined(OS_POSIX)
    FilePath path(FILE_PATH_LITERAL("/foo"));
  #endif
    scoped_refptr<Extension> extension_ptr(new Extension(path,
                                                         Extension::INVALID));
    Extension& extension = *extension_ptr;
    std::string error;
    DictionaryValue input_value;
  
    input_value.SetString(keys::kVersion, "1.0.0.0");
    // No strong RTL characters in name.
    std::wstring name(L"Dictionary (by Google)");
    input_value.SetString(keys::kName, WideToUTF16Hack(name));
    EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
                                        &error));
    EXPECT_EQ("", error);
    std::wstring localized_name(name);
    base::i18n::AdjustStringForLocaleDirection(&localized_name);
    EXPECT_EQ(localized_name, UTF8ToWide(extension.name()));
  
    // Strong RTL characters in name.
    name = L"Dictionary (\x05D1\x05D2"L" Google)";
    input_value.SetString(keys::kName, WideToUTF16Hack(name));
    EXPECT_TRUE(extension.InitFromValue(input_value, Extension::NO_FLAGS,
                                        &error));
    EXPECT_EQ("", error);
    localized_name = name;
    base::i18n::AdjustStringForLocaleDirection(&localized_name);
    EXPECT_EQ(localized_name, UTF8ToWide(extension.name()));
  
    // Reset locale.
  #if defined(TOOLKIT_GTK)
    gtk_widget_set_default_direction(gtk_dir);
  #else
    base::i18n::SetICUDefaultLocale(locale);
  #endif
  }
  
  TEST(ExtensionTest, GetResourceURLAndPath) {
  #if defined(OS_WIN)
    FilePath path(FILE_PATH_LITERAL("C:\\foo"));
  #elif defined(OS_POSIX)
    FilePath path(FILE_PATH_LITERAL("/foo"));
  #endif
    DictionaryValue input_value;
    input_value.SetString(keys::kVersion, "1.0.0.0");
    input_value.SetString(keys::kName, "my extension");
    scoped_refptr<Extension> extension(Extension::Create(path,
        Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS, NULL));
    EXPECT_TRUE(extension.get());
  
    EXPECT_EQ(extension->url().spec() + "bar/baz.js",
              Extension::GetResourceURL(extension->url(), "bar/baz.js").spec());
    EXPECT_EQ(extension->url().spec() + "baz.js",
              Extension::GetResourceURL(extension->url(),
                                        "bar/../baz.js").spec());
    EXPECT_EQ(extension->url().spec() + "baz.js",
              Extension::GetResourceURL(extension->url(), "../baz.js").spec());
  }
  
  TEST(ExtensionTest, LoadPageActionHelper) {
  #if defined(OS_WIN)
      FilePath path(base::StringPrintf(L"c:\\extension"));
  #else
      FilePath path(base::StringPrintf("/extension"));
  #endif
    scoped_refptr<Extension> extension_ptr(new Extension(path,
                                                         Extension::INVALID));
    Extension& extension = *extension_ptr;
    std::string error_msg;
    scoped_ptr<ExtensionAction> action;
    DictionaryValue input;
  
    // First try with an empty dictionary.
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(action != NULL);
    ASSERT_TRUE(error_msg.empty());
  
    // Now setup some values to use in the action.
    const std::string id("MyExtensionActionId");
    const std::string name("MyExtensionActionName");
    std::string img1("image1.png");
    std::string img2("image2.png");
  
    // Add the dictionary for the contextual action.
    input.SetString(keys::kPageActionId, id);
    input.SetString(keys::kName, name);
    ListValue* icons = new ListValue;
    icons->Set(0, Value::CreateStringValue(img1));
    icons->Set(1, Value::CreateStringValue(img2));
    input.Set(keys::kPageActionIcons, icons);
  
    // Parse and read back the values from the object.
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_TRUE(error_msg.empty());
    ASSERT_EQ(id, action->id());
    // No title, so fall back to name.
    ASSERT_EQ(name, action->GetTitle(1));
    ASSERT_EQ(2u, action->icon_paths()->size());
    ASSERT_EQ(img1, (*action->icon_paths())[0]);
    ASSERT_EQ(img2, (*action->icon_paths())[1]);
  
    // Explicitly set the same type and parse again.
    input.SetString(keys::kType, values::kPageActionTypeTab);
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_TRUE(error_msg.empty());
  
    // Make a deep copy of the input and remove one key at a time and see if we
    // get the right error.
    scoped_ptr<DictionaryValue> copy;
  
    // First remove id key.
    copy.reset(input.DeepCopy());
    copy->Remove(keys::kPageActionId, NULL);
    action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg));
    ASSERT_TRUE(NULL != action.get());
  
    // Then remove the name key. It's optional, so no error.
    copy.reset(input.DeepCopy());
    copy->Remove(keys::kName, NULL);
    action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_TRUE(action->GetTitle(1).empty());
    ASSERT_TRUE(error_msg.empty());
  
    // Then remove the icon paths key.
    copy.reset(input.DeepCopy());
    copy->Remove(keys::kPageActionIcons, NULL);
    action.reset(extension.LoadExtensionActionHelper(copy.get(), &error_msg));
    ASSERT_TRUE(NULL != action.get());
    error_msg = "";
  
    // Now test that we can parse the new format for page actions.
  
    // Now setup some values to use in the page action.
    const std::string kTitle("MyExtensionActionTitle");
    const std::string kIcon("image1.png");
    const std::string kPopupHtmlFile("a_popup.html");
  
    // Add the dictionary for the contextual action.
    input.Clear();
    input.SetString(keys::kPageActionDefaultTitle, kTitle);
    input.SetString(keys::kPageActionDefaultIcon, kIcon);
  
    // Parse and read back the values from the object.
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(action.get());
    ASSERT_TRUE(error_msg.empty());
    ASSERT_EQ(kTitle, action->GetTitle(1));
    ASSERT_EQ(0u, action->icon_paths()->size());
  
    // Invalid title should give an error even with a valid name.
    input.Clear();
    input.SetInteger(keys::kPageActionDefaultTitle, 42);
    input.SetString(keys::kName, name);
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL == action.get());
    ASSERT_STREQ(errors::kInvalidPageActionDefaultTitle, error_msg.c_str());
    error_msg = "";
  
    // Invalid name should give an error only with no title.
    input.SetString(keys::kPageActionDefaultTitle, kTitle);
    input.SetInteger(keys::kName, 123);
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_EQ(kTitle, action->GetTitle(1));
    ASSERT_TRUE(error_msg.empty());
  
    input.Remove(keys::kPageActionDefaultTitle, NULL);
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL == action.get());
    ASSERT_STREQ(errors::kInvalidPageActionName, error_msg.c_str());
    error_msg = "";
  
    // Test that keys "popup" and "default_popup" both work, but can not
    // be used at the same time.
    input.Clear();
    input.SetString(keys::kPageActionDefaultTitle, kTitle);
    input.SetString(keys::kPageActionDefaultIcon, kIcon);
  
    // LoadExtensionActionHelper expects the extension member |extension_url|
    // to be set.
    extension.extension_url_ =
        GURL(std::string(chrome::kExtensionScheme) +
             chrome::kStandardSchemeSeparator +
             "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/");
  
    // Add key "popup", expect success.
    input.SetString(keys::kPageActionPopup, kPopupHtmlFile);
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_TRUE(error_msg.empty());
    ASSERT_STREQ(
        extension.url().Resolve(kPopupHtmlFile).spec().c_str(),
        action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
  
    // Add key "default_popup", expect failure.
    input.SetString(keys::kPageActionDefaultPopup, kPopupHtmlFile);
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL == action.get());
    ASSERT_STREQ(
        ExtensionErrorUtils::FormatErrorMessage(
            errors::kInvalidPageActionOldAndNewKeys,
            keys::kPageActionDefaultPopup,
            keys::kPageActionPopup).c_str(),
        error_msg.c_str());
    error_msg = "";
  
    // Remove key "popup", expect success.
    input.Remove(keys::kPageActionPopup, NULL);
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_TRUE(error_msg.empty());
    ASSERT_STREQ(
        extension.url().Resolve(kPopupHtmlFile).spec().c_str(),
        action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
  
    // Setting default_popup to "" is the same as having no popup.
    input.Remove(keys::kPageActionDefaultPopup, NULL);
    input.SetString(keys::kPageActionDefaultPopup, "");
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_TRUE(error_msg.empty());
    EXPECT_FALSE(action->HasPopup(ExtensionAction::kDefaultTabId));
    ASSERT_STREQ(
        "",
        action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
  
    // Setting popup to "" is the same as having no popup.
    input.Remove(keys::kPageActionDefaultPopup, NULL);
    input.SetString(keys::kPageActionPopup, "");
    action.reset(extension.LoadExtensionActionHelper(&input, &error_msg));
    ASSERT_TRUE(NULL != action.get());
    ASSERT_TRUE(error_msg.empty());
    EXPECT_FALSE(action->HasPopup(ExtensionAction::kDefaultTabId));
    ASSERT_STREQ(
        "",
        action->GetPopupUrl(ExtensionAction::kDefaultTabId).spec().c_str());
  }
  
  TEST(ExtensionTest, IdIsValid) {
    EXPECT_TRUE(Extension::IdIsValid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
    EXPECT_TRUE(Extension::IdIsValid("pppppppppppppppppppppppppppppppp"));
    EXPECT_TRUE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnop"));
    EXPECT_TRUE(Extension::IdIsValid("ABCDEFGHIJKLMNOPABCDEFGHIJKLMNOP"));
    EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno"));
    EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnopa"));
    EXPECT_FALSE(Extension::IdIsValid("0123456789abcdef0123456789abcdef"));
    EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmnoq"));
    EXPECT_FALSE(Extension::IdIsValid("abcdefghijklmnopabcdefghijklmno0"));
  }
  
  TEST(ExtensionTest, GenerateID) {
    const uint8 public_key_info[] = {
      0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
      0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81,
      0x89, 0x02, 0x81, 0x81, 0x00, 0xb8, 0x7f, 0x2b, 0x20, 0xdc, 0x7c, 0x9b,
      0x0c, 0xdc, 0x51, 0x61, 0x99, 0x0d, 0x36, 0x0f, 0xd4, 0x66, 0x88, 0x08,
      0x55, 0x84, 0xd5, 0x3a, 0xbf, 0x2b, 0xa4, 0x64, 0x85, 0x7b, 0x0c, 0x04,
      0x13, 0x3f, 0x8d, 0xf4, 0xbc, 0x38, 0x0d, 0x49, 0xfe, 0x6b, 0xc4, 0x5a,
      0xb0, 0x40, 0x53, 0x3a, 0xd7, 0x66, 0x09, 0x0f, 0x9e, 0x36, 0x74, 0x30,
      0xda, 0x8a, 0x31, 0x4f, 0x1f, 0x14, 0x50, 0xd7, 0xc7, 0x20, 0x94, 0x17,
      0xde, 0x4e, 0xb9, 0x57, 0x5e, 0x7e, 0x0a, 0xe5, 0xb2, 0x65, 0x7a, 0x89,
      0x4e, 0xb6, 0x47, 0xff, 0x1c, 0xbd, 0xb7, 0x38, 0x13, 0xaf, 0x47, 0x85,
      0x84, 0x32, 0x33, 0xf3, 0x17, 0x49, 0xbf, 0xe9, 0x96, 0xd0, 0xd6, 0x14,
      0x6f, 0x13, 0x8d, 0xc5, 0xfc, 0x2c, 0x72, 0xba, 0xac, 0xea, 0x7e, 0x18,
      0x53, 0x56, 0xa6, 0x83, 0xa2, 0xce, 0x93, 0x93, 0xe7, 0x1f, 0x0f, 0xe6,
      0x0f, 0x02, 0x03, 0x01, 0x00, 0x01
    };
  
    std::string extension_id;
    EXPECT_TRUE(
        Extension::GenerateId(
            std::string(reinterpret_cast<const char*>(&public_key_info[0]),
                        arraysize(public_key_info)),
            &extension_id));
    EXPECT_EQ("melddjfinppjdikinhbgehiennejpfhp", extension_id);
  }
  
  TEST(ExtensionTest, UpdateUrls) {
    // Test several valid update urls
    std::vector<std::string> valid;
    valid.push_back("http://test.com");
    valid.push_back("http://test.com/");
    valid.push_back("http://test.com/update");
    valid.push_back("http://test.com/update?check=true");
    for (size_t i = 0; i < valid.size(); i++) {
      GURL url(valid[i]);
      EXPECT_TRUE(url.is_valid());
  
      DictionaryValue input_value;
  #if defined(OS_WIN)
      // (Why %Iu below?  This is the single file in the whole code base that
      // might make use of a WidePRIuS; let's not encourage any more.)
      FilePath path(base::StringPrintf(L"c:\\extension%Iu", i));
  #else
      FilePath path(base::StringPrintf("/extension%" PRIuS, i));
  #endif
      std::string error;
  
      input_value.SetString(keys::kVersion, "1.0");
      input_value.SetString(keys::kName, "Test");
      input_value.SetString(keys::kUpdateURL, url.spec());
  
      scoped_refptr<Extension> extension(Extension::Create(
          path, Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS,
          &error));
      EXPECT_TRUE(extension.get()) << error;
    }
  
    // Test some invalid update urls
    std::vector<std::string> invalid;
    invalid.push_back("");
    invalid.push_back("test.com");
    valid.push_back("http://test.com/update#whatever");
    for (size_t i = 0; i < invalid.size(); i++) {
      DictionaryValue input_value;
  #if defined(OS_WIN)
      // (Why %Iu below?  This is the single file in the whole code base that
      // might make use of a WidePRIuS; let's not encourage any more.)
      FilePath path(base::StringPrintf(L"c:\\extension%Iu", i));
  #else
      FilePath path(base::StringPrintf("/extension%" PRIuS, i));
  #endif
      std::string error;
      input_value.SetString(keys::kVersion, "1.0");
      input_value.SetString(keys::kName, "Test");
      input_value.SetString(keys::kUpdateURL, invalid[i]);
  
      scoped_refptr<Extension> extension(Extension::Create(
          path, Extension::INVALID, input_value, Extension::STRICT_ERROR_CHECKS,
          &error));
      EXPECT_FALSE(extension.get());
      EXPECT_TRUE(MatchPattern(error, errors::kInvalidUpdateURL));
    }
  }
  
  // This test ensures that the mimetype sniffing code stays in sync with the
  // actual crx files that we test other parts of the system with.
  TEST(ExtensionTest, MimeTypeSniffing) {
    FilePath path;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
    path = path.AppendASCII("extensions").AppendASCII("good.crx");
  
    std::string data;
    ASSERT_TRUE(file_util::ReadFileToString(path, &data));
  
    std::string result;
    EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(),
                GURL("http://www.example.com/foo.crx"), "", &result));
    EXPECT_EQ(std::string(Extension::kMimeType), result);
  
    data.clear();
    result.clear();
    path = path.DirName().AppendASCII("bad_magic.crx");
    ASSERT_TRUE(file_util::ReadFileToString(path, &data));
    EXPECT_TRUE(net::SniffMimeType(data.c_str(), data.size(),
                GURL("http://www.example.com/foo.crx"), "", &result));
    EXPECT_EQ("application/octet-stream", result);
  }
  
  static scoped_refptr<Extension> LoadManifest(const std::string& dir,
                                               const std::string& test_file,
                                               int extra_flags) {
    FilePath path;
    PathService::Get(chrome::DIR_TEST_DATA, &path);
    path = path.AppendASCII("extensions")
               .AppendASCII(dir)
               .AppendASCII(test_file);
  
    JSONFileValueSerializer serializer(path);
    std::string error;
    scoped_ptr<Value> result(serializer.Deserialize(NULL, &error));
    if (!result.get()) {
      EXPECT_EQ("", error);
      return NULL;
    }
  
    scoped_refptr<Extension> extension = Extension::Create(
        path.DirName(), Extension::INVALID,
        *static_cast<DictionaryValue*>(result.get()),
        Extension::STRICT_ERROR_CHECKS | extra_flags, &error);
    EXPECT_TRUE(extension) << error;
    return extension;
  }
  
  static scoped_refptr<Extension> LoadManifest(const std::string& dir,
                                               const std::string& test_file) {
    return LoadManifest(dir, test_file, Extension::NO_FLAGS);
  }
  
  TEST(ExtensionTest, EffectiveHostPermissions) {
    scoped_refptr<Extension> extension;
    ExtensionExtent hosts;
  
    extension = LoadManifest("effective_host_permissions", "empty.json");
    EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size());
    EXPECT_FALSE(hosts.ContainsURL(GURL("http://www.google.com")));
    EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions", "one_host.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
    EXPECT_FALSE(hosts.ContainsURL(GURL("https://www.google.com")));
    EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions",
                             "one_host_wildcard.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://foo.google.com")));
    EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions", "two_hosts.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.reddit.com")));
    EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions",
                             "https_not_considered.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("https://google.com")));
    EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions",
                             "two_content_scripts.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://google.com")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.reddit.com")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://news.ycombinator.com")));
    EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions", "all_hosts.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://test/")));
    EXPECT_FALSE(hosts.ContainsURL(GURL("https://test/")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
    EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions", "all_hosts2.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://test/")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
    EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
  
    extension = LoadManifest("effective_host_permissions", "all_hosts3.json");
    hosts = extension->GetEffectiveHostPermissions();
    EXPECT_FALSE(hosts.ContainsURL(GURL("http://test/")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("https://test/")));
    EXPECT_TRUE(hosts.ContainsURL(GURL("http://www.google.com")));
    EXPECT_TRUE(extension->HasEffectiveAccessToAllHosts());
  }
  
  TEST(ExtensionTest, IsPrivilegeIncrease) {
    const struct {
      const char* base_name;
      // Increase these sizes if you have more than 10.
      const char* granted_apis[10];
      const char* granted_hosts[10];
      bool full_access;
      bool expect_increase;
    } kTests[] = {
      { "allhosts1", {NULL}, {"http://*/", NULL}, false,
        false },  // all -> all
      { "allhosts2", {NULL}, {"http://*/", NULL}, false,
        false },  // all -> one
      { "allhosts3", {NULL}, {NULL}, false, true },  // one -> all
      { "hosts1", {NULL},
        {"http://www.google.com/", "http://www.reddit.com/", NULL}, false,
        false },  // http://a,http://b -> http://a,http://b
      { "hosts2", {NULL},
        {"http://www.google.com/", "http://www.reddit.com/", NULL}, false,
        true },  // http://a,http://b -> https://a,http://*.b
      { "hosts3", {NULL},
        {"http://www.google.com/", "http://www.reddit.com/", NULL}, false,
        false },  // http://a,http://b -> http://a
      { "hosts4", {NULL},
        {"http://www.google.com/", NULL}, false,
        true },  // http://a -> http://a,http://b
      { "hosts5", {"tabs", "notifications", NULL},
        {"http://*.example.com/", "http://*.example.com/*",
         "http://*.example.co.uk/*", "http://*.example.com.au/*",
         NULL}, false,
        false },  // http://a,b,c -> http://a,b,c + https://a,b,c
      { "hosts6", {"tabs", "notifications", NULL},
        {"http://*.example.com/", "http://*.example.com/*", NULL}, false,
        false },  // http://a.com -> http://a.com + http://a.co.uk
      { "permissions1", {"tabs", NULL},
        {NULL}, false, false },  // tabs -> tabs
      { "permissions2", {"tabs", NULL},
        {NULL}, false, true },  // tabs -> tabs,bookmarks
      { "permissions3", {NULL},
        {"http://*/*", NULL},
        false, true },  // http://a -> http://a,tabs
      { "permissions5", {"bookmarks", NULL},
        {NULL}, false, true },  // bookmarks -> bookmarks,history
  #if !defined(OS_CHROMEOS)  // plugins aren't allowed in ChromeOS
      { "permissions4", {NULL},
        {NULL}, true, false },  // plugin -> plugin,tabs
      { "plugin1", {NULL},
        {NULL}, true, false },  // plugin -> plugin
      { "plugin2", {NULL},
        {NULL}, true, false },  // plugin -> none
      { "plugin3", {NULL},
        {NULL}, false, true },  // none -> plugin
  #endif
      { "storage", {NULL},
        {NULL}, false, false },  // none -> storage
      { "notifications", {NULL},
        {NULL}, false, false }  // none -> notifications
    };
  
    for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
      scoped_refptr<Extension> old_extension(
          LoadManifest("allow_silent_upgrade",
                       std::string(kTests[i].base_name) + "_old.json"));
      scoped_refptr<Extension> new_extension(
          LoadManifest("allow_silent_upgrade",
                       std::string(kTests[i].base_name) + "_new.json"));
  
      std::set<std::string> granted_apis;
      for (size_t j = 0; kTests[i].granted_apis[j] != NULL; ++j)
        granted_apis.insert(kTests[i].granted_apis[j]);
  
      ExtensionExtent granted_hosts;
      for (size_t j = 0; kTests[i].granted_hosts[j] != NULL; ++j)
        AddPattern(&granted_hosts, kTests[i].granted_hosts[j]);
  
      EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json";
      if (!new_extension.get())
        continue;
  
      EXPECT_EQ(kTests[i].expect_increase,
                Extension::IsPrivilegeIncrease(kTests[i].full_access,
                                               granted_apis,
                                               granted_hosts,
                                               new_extension.get()))
          << kTests[i].base_name;
    }
  }
  
  TEST(ExtensionTest, PermissionMessages) {
    // Ensure that all permissions that needs to show install UI actually have
    // strings associated with them.
  
    std::set<std::string> skip;
  
    // These are considered "nuisance" or "trivial" permissions that don't need
    // a prompt.
    skip.insert(Extension::kContextMenusPermission);
    skip.insert(Extension::kIdlePermission);
    skip.insert(Extension::kNotificationPermission);
    skip.insert(Extension::kUnlimitedStoragePermission);
    skip.insert(Extension::kContentSettingsPermission);
  
    // TODO(erikkay) add a string for this permission.
    skip.insert(Extension::kBackgroundPermission);
  
    // The cookie permission does nothing unless you have associated host
    // permissions.
    skip.insert(Extension::kCookiePermission);
  
    // The proxy permission is warned as part of host permission checks.
    skip.insert(Extension::kProxyPermission);
  
    // This permission requires explicit user action (context menu handler)
    // so we won't prompt for it for now.
    skip.insert(Extension::kFileBrowserHandlerPermission);
  
    // If you've turned on the experimental command-line flag, we don't need
    // to warn you further.
    skip.insert(Extension::kExperimentalPermission);
  
    // These are only usable by component extensions.
    skip.insert(Extension::kWebstorePrivatePermission);
    skip.insert(Extension::kFileBrowserPrivatePermission);
    skip.insert(Extension::kChromeosInfoPrivatePermissions);
  
    const Extension::PermissionMessage::MessageId ID_NONE =
        Extension::PermissionMessage::ID_NONE;
  
    for (size_t i = 0; i < Extension::kNumPermissions; ++i) {
      Extension::Permission permission = Extension::kPermissions[i];
      if (skip.count(permission.name)) {
        EXPECT_EQ(ID_NONE, permission.message_id)
            << "unexpected message_id for " << permission.name;
      } else {
        EXPECT_NE(ID_NONE, permission.message_id)
            << "missing message_id for " << permission.name;
      }
    }
  }
  
  // Returns a copy of |source| resized to |size| x |size|.
  static SkBitmap ResizedCopy(const SkBitmap& source, int size) {
    return skia::ImageOperations::Resize(source,
                                         skia::ImageOperations::RESIZE_LANCZOS3,
                                         size,
                                         size);
  }
  
  static bool SizeEquals(const SkBitmap& bitmap, const gfx::Size& size) {
    return bitmap.width() == size.width() && bitmap.height() == size.height();
  }
  
  TEST(ExtensionTest, ImageCaching) {
    FilePath path;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path));
    path = path.AppendASCII("extensions");
  
    // Initialize the Extension.
    std::string errors;
    DictionaryValue values;
    values.SetString(keys::kName, "test");
    values.SetString(keys::kVersion, "0.1");
    scoped_refptr<Extension> extension(Extension::Create(
        path, Extension::INVALID, values, Extension::STRICT_ERROR_CHECKS,
        &errors));
    ASSERT_TRUE(extension.get());
  
    // Create an ExtensionResource pointing at an icon.
    FilePath icon_relative_path(FILE_PATH_LITERAL("icon3.png"));
    ExtensionResource resource(extension->id(),
                               extension->path(),
                               icon_relative_path);
  
    // Read in the icon file.
    FilePath icon_absolute_path = extension->path().Append(icon_relative_path);
    std::string raw_png;
    ASSERT_TRUE(file_util::ReadFileToString(icon_absolute_path, &raw_png));
    SkBitmap image;
    ASSERT_TRUE(gfx::PNGCodec::Decode(
        reinterpret_cast<const unsigned char*>(raw_png.data()),
        raw_png.length(),
        &image));
  
    // Make sure the icon file is the size we expect.
    gfx::Size original_size(66, 66);
    ASSERT_EQ(image.width(), original_size.width());
    ASSERT_EQ(image.height(), original_size.height());
  
    // Create two resized versions at size 16x16 and 24x24.
    SkBitmap image16 = ResizedCopy(image, 16);
    SkBitmap image24 = ResizedCopy(image, 24);
  
    gfx::Size size16(16, 16);
    gfx::Size size24(24, 24);
  
    // Cache the 16x16 copy.
    EXPECT_FALSE(extension->HasCachedImage(resource, size16));
    extension->SetCachedImage(resource, image16, original_size);
    EXPECT_TRUE(extension->HasCachedImage(resource, size16));
    EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size16), size16));
    EXPECT_FALSE(extension->HasCachedImage(resource, size24));
    EXPECT_FALSE(extension->HasCachedImage(resource, original_size));
  
    // Cache the 24x24 copy.
    extension->SetCachedImage(resource, image24, original_size);
    EXPECT_TRUE(extension->HasCachedImage(resource, size24));
    EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size24), size24));
    EXPECT_FALSE(extension->HasCachedImage(resource, original_size));
  
    // Cache the original, and verify that it gets returned when we ask for a
    // max_size that is larger than the original.
    gfx::Size size128(128, 128);
    EXPECT_TRUE(image.width() < size128.width() &&
                image.height() < size128.height());
    extension->SetCachedImage(resource, image, original_size);
    EXPECT_TRUE(extension->HasCachedImage(resource, original_size));
    EXPECT_TRUE(extension->HasCachedImage(resource, size128));
    EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, original_size),
                           original_size));
    EXPECT_TRUE(SizeEquals(extension->GetCachedImage(resource, size128),
                           original_size));
    EXPECT_EQ(extension->GetCachedImage(resource, original_size).getPixels(),
              extension->GetCachedImage(resource, size128).getPixels());
  }
  
  // Tests that the old permission name "unlimited_storage" still works for
  // backwards compatibility (we renamed it to "unlimitedStorage").
  TEST(ExtensionTest, OldUnlimitedStoragePermission) {
    ScopedTempDir directory;
    ASSERT_TRUE(directory.CreateUniqueTempDir());
    FilePath extension_path = directory.path();
    DictionaryValue dictionary;
  
    // The two required keys.
    dictionary.SetString(extension_manifest_keys::kName, "test");
    dictionary.SetString(extension_manifest_keys::kVersion, "0.1");
  
    // Create a permissions list containing "unlimited_storage" and add it.
    ListValue* permissions = new ListValue();
    const char* old_unlimited = "unlimited_storage";
    EXPECT_STREQ(old_unlimited, Extension::kOldUnlimitedStoragePermission);
    permissions->Append(Value::CreateStringValue(old_unlimited));
    dictionary.Set(extension_manifest_keys::kPermissions, permissions);
  
    // Initialize the extension and make sure the permission for unlimited storage
    // is present.
    std::string errors;
    scoped_refptr<Extension> extension(Extension::Create(
        extension_path, Extension::INVALID, dictionary,
        Extension::STRICT_ERROR_CHECKS, &errors));
    EXPECT_TRUE(extension.get());
    EXPECT_TRUE(extension->HasApiPermission(
        Extension::kUnlimitedStoragePermission));
  }
  
  // This tests the API permissions with an empty manifest (one that just
  // specifies a name and a version and nothing else).
  TEST(ExtensionTest, ApiPermissions) {
    const struct {
      const char* permission_name;
      bool expect_success;
    } kTests[] = {
      // Negative test.
      { "non_existing_permission", false },
      // Test default module/package permission.
      { "browserAction",  true },
      { "browserActions", true },
      { "devtools",       true },
      { "extension",      true },
      { "i18n",           true },
      { "pageAction",     true },
      { "pageActions",    true },
      { "test",           true },
      // Some negative tests.
      { "bookmarks",      false },
      { "cookies",        false },
      { "history",        false },
      { "tabs.onUpdated", false },
      // Make sure we find the module name after stripping '.' and '/'.
      { "browserAction/abcd/onClick",  true },
      { "browserAction.abcd.onClick",  true },
      // Test Tabs functions.
      { "tabs.create",      true},
      { "tabs.update",      true},
      { "tabs.getSelected", false},
    };
  
    scoped_refptr<Extension> extension;
    extension = LoadManifest("empty_manifest", "empty.json");
  
    for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
      EXPECT_EQ(kTests[i].expect_success,
                extension->HasApiPermission(kTests[i].permission_name))
                    << "Permission being tested: " << kTests[i].permission_name;
    }
  }
  
  TEST(ExtensionTest, GetHostPermissionMessages_ManyHosts) {
    scoped_refptr<Extension> extension;
    extension = LoadManifest("permissions", "many-hosts.json");
    std::vector<string16> warnings = extension->GetPermissionMessageStrings();
    ASSERT_EQ(1u, warnings.size());
    EXPECT_EQ("Your data on www.google.com and encrypted.google.com",
              UTF16ToUTF8(warnings[0]));
  }
  
  TEST(ExtensionTest, GetPermissionMessages_Plugins) {
    scoped_refptr<Extension> extension;
    extension = LoadManifest("permissions", "plugins.json");
    std::vector<string16> warnings = extension->GetPermissionMessageStrings();
    // We don't parse the plugins key on Chrome OS, so it should not ask for any
    // permissions.
  #if defined(OS_CHROMEOS)
    ASSERT_EQ(0u, warnings.size());
  #else
    ASSERT_EQ(1u, warnings.size());
    EXPECT_EQ("All data on your computer and the websites you visit",
              UTF16ToUTF8(warnings[0]));
  #endif
  }
  
  TEST(ExtensionTest, WantsFileAccess) {
    scoped_refptr<Extension> extension;
    GURL file_url("file:///etc/passwd");
  
    // <all_urls> permission
    extension = LoadManifest("permissions", "permissions_all_urls.json");
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
    extension = LoadManifest(
        "permissions", "permissions_all_urls.json", Extension::ALLOW_FILE_ACCESS);
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
  
    // file:///* permission
    extension = LoadManifest("permissions", "permissions_file_scheme.json");
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
    extension = LoadManifest("permissions", "permissions_file_scheme.json",
        Extension::ALLOW_FILE_ACCESS);
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_TRUE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
  
    // http://* permission
    extension = LoadManifest("permissions", "permissions_http_scheme.json");
    EXPECT_FALSE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
    extension = LoadManifest("permissions", "permissions_http_scheme.json",
        Extension::ALLOW_FILE_ACCESS);
    EXPECT_FALSE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(file_url, NULL, NULL));
  
    // <all_urls> content script match
    extension = LoadManifest("permissions", "content_script_all_urls.json");
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(
        file_url, &extension->content_scripts()[0], NULL));
    extension = LoadManifest("permissions", "content_script_all_urls.json",
        Extension::ALLOW_FILE_ACCESS);
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_TRUE(extension->CanExecuteScriptOnPage(
        file_url, &extension->content_scripts()[0], NULL));
  
    // file:///* content script match
    extension = LoadManifest("permissions", "content_script_file_scheme.json");
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(
        file_url, &extension->content_scripts()[0], NULL));
    extension = LoadManifest("permissions", "content_script_file_scheme.json",
        Extension::ALLOW_FILE_ACCESS);
    EXPECT_TRUE(extension->wants_file_access());
    EXPECT_TRUE(extension->CanExecuteScriptOnPage(
        file_url, &extension->content_scripts()[0], NULL));
  
    // http://* content script match
    extension = LoadManifest("permissions", "content_script_http_scheme.json");
    EXPECT_FALSE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(
        file_url, &extension->content_scripts()[0], NULL));
    extension = LoadManifest("permissions", "content_script_http_scheme.json",
        Extension::ALLOW_FILE_ACCESS);
    EXPECT_FALSE(extension->wants_file_access());
    EXPECT_FALSE(extension->CanExecuteScriptOnPage(
        file_url, &extension->content_scripts()[0], NULL));
  }
  
  // Base class for testing the CanExecuteScriptOnPage and CanCaptureVisiblePage
  // methods of Extension for extensions with various permissions.
  class ExtensionScriptAndCaptureVisibleTest : public testing::Test {
   public:
    ExtensionScriptAndCaptureVisibleTest() {
      PathService::Get(chrome::DIR_TEST_DATA, &dirpath_);
    }
  
    scoped_refptr<Extension> MakeExtension(const std::string& permissions,
                                           Extension::Location location) {
      // Replace single-quotes with double-quotes in permissions, since JSON
      // mandates double-quotes.
      std::string munged_permissions = permissions;
      ReplaceSubstringsAfterOffset(&munged_permissions, 0, "'", "\"");
  
      DictionaryValue dictionary;
      dictionary.SetString(keys::kName, "permission test");
      dictionary.SetString(keys::kVersion, "1");
      std::string error;
      JSONStringValueSerializer serializer(munged_permissions);
      scoped_ptr<Value> permission_value(serializer.Deserialize(NULL, &error));
      EXPECT_EQ("", error);
      if (!permission_value.get())
        return NULL;
      EXPECT_TRUE(permission_value->IsType(Value::TYPE_LIST));
      dictionary.Set(keys::kPermissions, permission_value.release());
  
      FilePath dirpath;
      PathService::Get(chrome::DIR_TEST_DATA, &dirpath);
      dirpath = dirpath.AppendASCII("extensions").AppendASCII("permissions");
  
      scoped_refptr<Extension> extension =  Extension::Create(
          dirpath,
          location,
          dictionary,
          Extension::STRICT_ERROR_CHECKS,
          &error);
      if (!extension)
        VLOG(1) << error;
      return extension;
    }
  
    bool Allowed(const Extension* extension, const GURL& url) {
      return (extension->CanExecuteScriptOnPage(url, NULL, NULL) &&
              extension->CanCaptureVisiblePage(url, NULL));
    }
  
    bool CaptureOnly(const Extension* extension, const GURL& url) {
      return !extension->CanExecuteScriptOnPage(url, NULL, NULL) &&
          extension->CanCaptureVisiblePage(url, NULL);
    }
  
    bool Blocked(const Extension* extension, const GURL& url) {
      return !(extension->CanExecuteScriptOnPage(url, NULL, NULL) ||
               extension->CanCaptureVisiblePage(url, NULL));
    }
  
   protected:
    FilePath dirpath_;
  };
  
  TEST_F(ExtensionScriptAndCaptureVisibleTest, Permissions) {
    scoped_refptr<Extension> extension;
    // URLs that are "safe" to provide scripting and capture visible tab access
    // to if the permissions allow it.
    GURL http_url("http://www.google.com");
    GURL https_url("https://www.google.com");
    GURL file_url("file:///foo/bar");
  
    // We should allow host permission but not scripting permission for favicon
    // urls.
    GURL favicon_url("chrome://favicon/http://www.google.com");
  
    std::string dummy_id =
        Extension::GenerateIdForPath(FilePath(FILE_PATH_LITERAL("whatever")));
  
    // URLs that regular extensions should never get access to.
    GURL extension_url("chrome-extension://" + dummy_id);
    GURL settings_url("chrome://settings");
    GURL about_url("about:flags");
  
    // Test <all_urls> for regular extensions.
    extension = MakeExtension("['tabs','<all_urls>']", Extension::INTERNAL);
    EXPECT_TRUE(Allowed(extension, http_url));
    EXPECT_TRUE(Allowed(extension, https_url));
    EXPECT_TRUE(Blocked(extension, file_url));
    EXPECT_TRUE(Blocked(extension, settings_url));
    EXPECT_TRUE(CaptureOnly(extension, favicon_url));
    EXPECT_TRUE(Blocked(extension, about_url));
    EXPECT_TRUE(Blocked(extension, extension_url));
  
    EXPECT_FALSE(extension->HasHostPermission(settings_url));
    EXPECT_FALSE(extension->HasHostPermission(about_url));
    EXPECT_TRUE(extension->HasHostPermission(favicon_url));
  
    // Test * for scheme, which implies just the http/https schemes.
    extension = MakeExtension("['tabs','*://*/']", Extension::INTERNAL);
    EXPECT_TRUE(Allowed(extension, http_url));
    EXPECT_TRUE(Allowed(extension, https_url));
    EXPECT_TRUE(Blocked(extension, settings_url));
    EXPECT_TRUE(Blocked(extension, about_url));
    EXPECT_TRUE(Blocked(extension, file_url));
    EXPECT_TRUE(Blocked(extension, favicon_url));
    extension = MakeExtension("['tabs','*://settings/*']", Extension::INTERNAL);
    EXPECT_TRUE(Blocked(extension, settings_url));
  
    // Having chrome://*/ should not work for regular extensions. Note that
    // for favicon access, we require the explicit pattern chrome://favicon/*.
    extension = MakeExtension("['tabs','chrome://*/']",
                              Extension::INTERNAL);
    EXPECT_TRUE(extension == NULL);
  
    // Having chrome://favicon/* should not give you chrome://*
    extension = MakeExtension("['tabs','chrome://favicon/*']",
                              Extension::INTERNAL);
    EXPECT_TRUE(Blocked(extension, settings_url));
    EXPECT_TRUE(CaptureOnly(extension, favicon_url));
    EXPECT_TRUE(Blocked(extension, about_url));
    EXPECT_TRUE(extension->HasHostPermission(favicon_url));
  
    // Having http://favicon should not give you chrome://favicon
    extension = MakeExtension("['tabs', 'http://favicon/']", Extension::INTERNAL);
    EXPECT_TRUE(Blocked(extension, settings_url));
    EXPECT_TRUE(Blocked(extension, favicon_url));
  
    // Component extensions with <all_urls> should get everything.
    extension = MakeExtension("['tabs','<all_urls>']", Extension::COMPONENT);
    EXPECT_TRUE(Allowed(extension, http_url));
    EXPECT_TRUE(Allowed(extension, https_url));
    EXPECT_TRUE(Allowed(extension, settings_url));
    EXPECT_TRUE(Allowed(extension, about_url));
    EXPECT_TRUE(Allowed(extension, favicon_url));
    EXPECT_TRUE(extension->HasHostPermission(favicon_url));
  
    // Component extensions should only get access to what they ask for.
    extension = MakeExtension("['tabs', 'http://www.google.com/']",
                              Extension::COMPONENT);
    EXPECT_TRUE(Allowed(extension, http_url));
    EXPECT_TRUE(Blocked(extension, https_url));
    EXPECT_TRUE(Blocked(extension, file_url));
    EXPECT_TRUE(Blocked(extension, settings_url));
    EXPECT_TRUE(Blocked(extension, favicon_url));
    EXPECT_TRUE(Blocked(extension, about_url));
    EXPECT_TRUE(Blocked(extension, extension_url));
    EXPECT_FALSE(extension->HasHostPermission(settings_url));
  }
  
  
  TEST(ExtensionTest, GetDistinctHostsForDisplay) {
    std::vector<std::string> expected;
    expected.push_back("www.foo.com");
    expected.push_back("www.bar.com");
    expected.push_back("www.baz.com");
    URLPatternList actual;
  
    {
      SCOPED_TRACE("no dupes");
  
      // Simple list with no dupes.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
      CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
    }
  
    {
      SCOPED_TRACE("two dupes");
  
      // Add some dupes.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path"));
      CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
    }
  
    {
      SCOPED_TRACE("schemes differ");
  
      // Add a pattern that differs only by scheme. This should be filtered out.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path"));
      CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
    }
  
    {
      SCOPED_TRACE("paths differ");
  
      // Add some dupes by path.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath"));
      CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
    }
  
    {
      SCOPED_TRACE("subdomains differ");
  
      // We don't do anything special for subdomains.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://monkey.www.bar.com/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://bar.com/path"));
  
      expected.push_back("monkey.www.bar.com");
      expected.push_back("bar.com");
  
      CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
    }
  
    {
      SCOPED_TRACE("RCDs differ");
  
      // Now test for RCD uniquing.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.de/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca.us/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com.my/path"));
  
      // This is an unknown RCD, which shouldn't be uniqued out.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));
      // But it should only occur once.
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.xyzzy/path"));
  
      expected.push_back("www.foo.xyzzy");
  
      CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
    }
  
    {
      SCOPED_TRACE("wildcards");
  
      actual.push_back(
          URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com/*"));
  
      expected.push_back("*.google.com");
  
      CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
    }
  }
  
  TEST(ExtensionTest, GetDistinctHostsForDisplay_ComIsBestRcd) {
    URLPatternList actual;
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path"));
  
    std::vector<std::string> expected;
    expected.push_back("www.foo.com");
  
    CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
  }
  
  TEST(ExtensionTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) {
    URLPatternList actual;
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.net/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
    // No http://www.foo.com/path
  
    std::vector<std::string> expected;
    expected.push_back("www.foo.net");
  
    CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
  }
  
  TEST(ExtensionTest, GetDistinctHostsForDisplay_OrgIs3rdBestRcd) {
    URLPatternList actual;
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.org/path"));
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
    // No http://www.foo.net/path
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
    // No http://www.foo.com/path
  
    std::vector<std::string> expected;
    expected.push_back("www.foo.org");
  
    CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
  }
  
  TEST(ExtensionTest, GetDistinctHostsForDisplay_FirstInListIs4thBestRcd) {
    URLPatternList actual;
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.ca/path"));
    // No http://www.foo.org/path
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.co.uk/path"));
    // No http://www.foo.net/path
    actual.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.jp/path"));
    // No http://www.foo.com/path
  
    std::vector<std::string> expected;
    expected.push_back("www.foo.ca");
  
    CompareLists(expected,
                   Extension::GetDistinctHostsForDisplay(actual));
  }
  
  TEST(ExtensionTest, IsElevatedHostList) {
    URLPatternList list1;
    URLPatternList list2;
  
    list1.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"));
    list1.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
  
    // Test that the host order does not matter.
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path"));
  
    EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2));
    EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
  
    // Test that paths are ignored.
    list2.clear();
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/*"));
    EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2));
    EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
  
    // Test that RCDs are ignored.
    list2.clear();
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*"));
    EXPECT_FALSE(Extension::IsElevatedHostList(list1, list2));
    EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
  
    // Test that subdomain wildcards are handled properly.
    list2.clear();
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*"));
    EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2));
    //TODO(jstritar): Does not match subdomains properly. http://crbug.com/65337
    //EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
  
    // Test that different domains count as different hosts.
    list2.clear();
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path"));
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://www.example.org/path"));
    EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2));
    EXPECT_FALSE(Extension::IsElevatedHostList(list2, list1));
  
    // Test that different subdomains count as different hosts.
    list2.clear();
    list2.push_back(
        URLPattern(URLPattern::SCHEME_HTTP, "http://mail.google.com/*"));
    EXPECT_TRUE(Extension::IsElevatedHostList(list1, list2));
    EXPECT_TRUE(Extension::IsElevatedHostList(list2, list1));
  }
  
  TEST(ExtensionTest, GenerateId) {
    std::string result;
    EXPECT_TRUE(Extension::GenerateId("", &result));
  
    EXPECT_TRUE(Extension::GenerateId("test", &result));
    EXPECT_EQ(result, "jpignaibiiemhngfjkcpokkamffknabf");
  
    EXPECT_TRUE(Extension::GenerateId("_", &result));
    EXPECT_EQ(result, "ncocknphbhhlhkikpnnlmbcnbgdempcd");
  
    EXPECT_TRUE(Extension::GenerateId(
        "this_string_is_longer_than_a_single_sha256_hash_digest", &result));
    EXPECT_EQ(result, "jimneklojkjdibfkgiiophfhjhbdgcfi");
  }
  