// Copyright 2013 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 "extensions/browser/process_map.h"

#include "base/memory/ref_counted.h"
#include "base/values.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {
namespace {

enum class TypeToCreate { kExtension, kHostedApp, kPlatformApp };

scoped_refptr<const Extension> CreateExtensionWithFlags(TypeToCreate type,
                                                        const std::string& id) {
  DictionaryBuilder manifest_builder;
  manifest_builder.Set("name", "Test extension")
      .Set("version", "1.0")
      .Set("manifest_version", 2);

  switch (type) {
    case TypeToCreate::kExtension:
      manifest_builder.Set(
          "background",
          DictionaryBuilder()
              .Set("scripts", ListBuilder().Append("background.js").Build())
              .Build());
      break;
    case TypeToCreate::kHostedApp:
      manifest_builder.Set(
          "app", DictionaryBuilder()
                     .Set("launch", DictionaryBuilder()
                                        .Set("web_url", "https://www.foo.bar")
                                        .Build())
                     .Build());
      break;
    case TypeToCreate::kPlatformApp:
      manifest_builder.Set(
          "app",
          DictionaryBuilder()
              .Set("background",
                   DictionaryBuilder()
                       .Set("scripts",
                            ListBuilder().Append("background.js").Build())
                       .Build())
              .Build());
      break;
  }

  return ExtensionBuilder()
      .SetID(id)
      .SetManifest(manifest_builder.Build())
      .Build();
}

}  // namespace
}  // namespace extensions

using extensions::ProcessMap;

TEST(ExtensionProcessMapTest, Test) {
  ProcessMap map;

  // Test behavior when empty.
  EXPECT_FALSE(map.Contains("a", 1));
  EXPECT_FALSE(map.Remove("a", 1, content::SiteInstanceId(1)));
  EXPECT_EQ(0u, map.size());

  // Test insertion and behavior with one item.
  EXPECT_TRUE(map.Insert("a", 1, content::SiteInstanceId(1)));
  EXPECT_TRUE(map.Contains("a", 1));
  EXPECT_FALSE(map.Contains("a", 2));
  EXPECT_FALSE(map.Contains("b", 1));
  EXPECT_EQ(1u, map.size());

  // Test inserting a duplicate item.
  EXPECT_FALSE(map.Insert("a", 1, content::SiteInstanceId(1)));
  EXPECT_TRUE(map.Contains("a", 1));
  EXPECT_EQ(1u, map.size());

  // Insert some more items.
  EXPECT_TRUE(map.Insert("a", 2, content::SiteInstanceId(2)));
  EXPECT_TRUE(map.Insert("b", 1, content::SiteInstanceId(3)));
  EXPECT_TRUE(map.Insert("b", 2, content::SiteInstanceId(4)));
  EXPECT_EQ(4u, map.size());

  EXPECT_TRUE(map.Contains("a", 1));
  EXPECT_TRUE(map.Contains("a", 2));
  EXPECT_TRUE(map.Contains("b", 1));
  EXPECT_TRUE(map.Contains("b", 2));
  EXPECT_FALSE(map.Contains("a", 3));

  // Note that this only differs from an existing item because of the site
  // instance id.
  EXPECT_TRUE(map.Insert("a", 1, content::SiteInstanceId(5)));
  EXPECT_TRUE(map.Contains("a", 1));

  // Test removal.
  EXPECT_TRUE(map.Remove("a", 1, content::SiteInstanceId(1)));
  EXPECT_FALSE(map.Remove("a", 1, content::SiteInstanceId(1)));
  EXPECT_EQ(4u, map.size());

  // Should still return true because there were two site instances for this
  // extension/process pair.
  EXPECT_TRUE(map.Contains("a", 1));

  EXPECT_TRUE(map.Remove("a", 1, content::SiteInstanceId(5)));
  EXPECT_EQ(3u, map.size());
  EXPECT_FALSE(map.Contains("a", 1));

  EXPECT_EQ(2, map.RemoveAllFromProcess(2));
  EXPECT_EQ(1u, map.size());
  EXPECT_EQ(0, map.RemoveAllFromProcess(2));
  EXPECT_EQ(1u, map.size());
}

TEST(ExtensionProcessMapTest, GetMostLikelyContextType) {
  ProcessMap map;
  const GURL web_url("https://foo.example");
  const GURL extension_url("chrome-extension://foobar");
  const GURL untrusted_webui_url("chrome-untrusted://foo/index.html");

  EXPECT_EQ(extensions::Feature::WEB_PAGE_CONTEXT,
            map.GetMostLikelyContextType(nullptr, 1, &web_url));

  scoped_refptr<const extensions::Extension> extension =
      CreateExtensionWithFlags(extensions::TypeToCreate::kExtension, "a");

  EXPECT_EQ(extensions::Feature::CONTENT_SCRIPT_CONTEXT,
            map.GetMostLikelyContextType(extension.get(), 2, &extension_url));

  EXPECT_EQ(extensions::Feature::CONTENT_SCRIPT_CONTEXT,
            map.GetMostLikelyContextType(extension.get(), 2, &web_url));

  EXPECT_EQ(
      extensions::Feature::CONTENT_SCRIPT_CONTEXT,
      map.GetMostLikelyContextType(extension.get(), 2, &untrusted_webui_url));

  map.Insert("b", 3, content::SiteInstanceId(1));
  extension =
      CreateExtensionWithFlags(extensions::TypeToCreate::kExtension, "b");
  EXPECT_EQ(extensions::Feature::BLESSED_EXTENSION_CONTEXT,
            map.GetMostLikelyContextType(extension.get(), 3, &extension_url));

  map.Insert("c", 4, content::SiteInstanceId(2));
  extension =
      CreateExtensionWithFlags(extensions::TypeToCreate::kPlatformApp, "c");
  EXPECT_EQ(extensions::Feature::BLESSED_EXTENSION_CONTEXT,
            map.GetMostLikelyContextType(extension.get(), 4, &extension_url));

  map.set_is_lock_screen_context(true);

  map.Insert("d", 5, content::SiteInstanceId(3));
  extension =
      CreateExtensionWithFlags(extensions::TypeToCreate::kPlatformApp, "d");
  EXPECT_EQ(extensions::Feature::LOCK_SCREEN_EXTENSION_CONTEXT,
            map.GetMostLikelyContextType(extension.get(), 5, &extension_url));

  map.Insert("e", 6, content::SiteInstanceId(4));
  extension =
      CreateExtensionWithFlags(extensions::TypeToCreate::kExtension, "e");
  EXPECT_EQ(extensions::Feature::LOCK_SCREEN_EXTENSION_CONTEXT,
            map.GetMostLikelyContextType(extension.get(), 6, &extension_url));

  map.Insert("f", 7, content::SiteInstanceId(5));
  extension =
      CreateExtensionWithFlags(extensions::TypeToCreate::kHostedApp, "f");
  EXPECT_EQ(extensions::Feature::BLESSED_WEB_PAGE_CONTEXT,
            map.GetMostLikelyContextType(extension.get(), 7, &web_url));

  map.Insert("g", 8, content::SiteInstanceId(6));
  EXPECT_EQ(extensions::Feature::WEBUI_UNTRUSTED_CONTEXT,
            map.GetMostLikelyContextType(/*extension=*/nullptr, 8,
                                         &untrusted_webui_url));

  map.Insert("h", 9, content::SiteInstanceId(7));
  EXPECT_EQ(extensions::Feature::WEB_PAGE_CONTEXT,
            map.GetMostLikelyContextType(/*extension=*/nullptr, 9, &web_url));
}
