blob: 4feb34f4baff225fd317c3c7fb85080bb8d44f54 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/extensions/api/extension_api.h"
#include <string>
#include <vector>
#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_builder.h"
#include "chrome/common/extensions/extension_test_util.h"
#include "chrome/common/extensions/features/api_feature.h"
#include "chrome/common/extensions/features/base_feature_provider.h"
#include "chrome/common/extensions/features/simple_feature.h"
#include "chrome/common/extensions/value_builder.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
using extension_test_util::BuildExtension;
SimpleFeature* CreateAPIFeature() {
return new APIFeature();
}
TEST(ExtensionAPITest, Creation) {
ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance();
EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance());
scoped_ptr<ExtensionAPI> new_instance(
ExtensionAPI::CreateWithDefaultConfiguration());
EXPECT_NE(new_instance.get(),
scoped_ptr<ExtensionAPI>(
ExtensionAPI::CreateWithDefaultConfiguration()).get());
ExtensionAPI empty_instance;
struct {
ExtensionAPI* api;
bool expect_populated;
} test_data[] = {
{ shared_instance, true },
{ new_instance.get(), true },
{ &empty_instance, false }
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
EXPECT_EQ(test_data[i].expect_populated,
test_data[i].api->GetSchema("bookmarks.create") != NULL);
}
}
TEST(ExtensionAPITest, SplitDependencyName) {
struct {
std::string input;
std::string expected_feature_type;
std::string expected_feature_name;
} test_data[] = {
{ "", "api", "" }, // assumes "api" when no type is present
{ "foo", "api", "foo" },
{ "foo:", "foo", "" },
{ ":foo", "", "foo" },
{ "foo:bar", "foo", "bar" },
{ "foo:bar.baz", "foo", "bar.baz" }
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
std::string feature_type;
std::string feature_name;
ExtensionAPI::SplitDependencyName(test_data[i].input, &feature_type,
&feature_name);
EXPECT_EQ(test_data[i].expected_feature_type, feature_type) << i;
EXPECT_EQ(test_data[i].expected_feature_name, feature_name) << i;
}
}
TEST(ExtensionAPITest, IsPrivileged) {
scoped_ptr<ExtensionAPI> extension_api(
ExtensionAPI::CreateWithDefaultConfiguration());
EXPECT_FALSE(extension_api->IsPrivileged("runtime.connect"));
EXPECT_FALSE(extension_api->IsPrivileged("runtime.onConnect"));
EXPECT_FALSE(extension_api->IsPrivileged("runtime.lastError"));
// Exists, but privileged.
EXPECT_TRUE(extension_api->IsPrivileged("extension.getViews"));
EXPECT_TRUE(extension_api->IsPrivileged("history.search"));
// Whole APIs that are unprivileged.
EXPECT_FALSE(extension_api->IsPrivileged("app.getDetails"));
EXPECT_FALSE(extension_api->IsPrivileged("app.isInstalled"));
EXPECT_FALSE(extension_api->IsPrivileged("storage.local"));
EXPECT_FALSE(extension_api->IsPrivileged("storage.local.onChanged"));
EXPECT_FALSE(extension_api->IsPrivileged("storage.local.set"));
EXPECT_FALSE(extension_api->IsPrivileged("storage.local.MAX_ITEMS"));
EXPECT_FALSE(extension_api->IsPrivileged("storage.set"));
}
TEST(ExtensionAPITest, IsPrivilegedFeatures) {
struct {
std::string api_full_name;
bool expect_is_privilged;
} test_data[] = {
{ "test1", false },
{ "test1.foo", true },
{ "test2", true },
{ "test2.foo", false },
{ "test2.bar", false },
{ "test2.baz", true },
{ "test3", false },
{ "test3.foo", true },
{ "test4", false }
};
base::FilePath api_features_path;
PathService::Get(chrome::DIR_TEST_DATA, &api_features_path);
api_features_path = api_features_path.AppendASCII("extensions")
.AppendASCII("extension_api_unittest")
.AppendASCII("privileged_api_features.json");
std::string api_features_str;
ASSERT_TRUE(file_util::ReadFileToString(
api_features_path, &api_features_str)) << "privileged_api_features.json";
scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>(
base::JSONReader::Read(api_features_str)));
BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature);
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
ExtensionAPI api;
api.RegisterDependencyProvider("api", &api_feature_provider);
EXPECT_EQ(test_data[i].expect_is_privilged,
api.IsPrivileged(test_data[i].api_full_name)) << i;
}
}
TEST(ExtensionAPITest, APIFeatures) {
struct {
std::string api_full_name;
bool expect_is_available;
Feature::Context context;
GURL url;
} test_data[] = {
{ "test1", false, Feature::WEB_PAGE_CONTEXT, GURL() },
{ "test1", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
{ "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
{ "test1", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "test2", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") },
{ "test2", false, Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://google.com") },
{ "test2.foo", false, Feature::WEB_PAGE_CONTEXT,
GURL("http://google.com") },
{ "test2.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "test3", false, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") },
{ "test3.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") },
{ "test3.foo", true, Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://bad.com") },
{ "test4", true, Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://bad.com") },
{ "test4.foo", false, Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://bad.com") },
{ "test4.foo", false, Feature::UNBLESSED_EXTENSION_CONTEXT,
GURL("http://bad.com") },
{ "test4.foo.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "test5", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
{ "test5", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
{ "test5.blah", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
{ "test5.blah", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
{ "test6", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
{ "test6.foo", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
{ "test7", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
{ "test7.foo", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
{ "test7.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
{ "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
{ "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
// Test parent/child.
{ "parent1", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent1", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
{ "parent1.child1", false, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent1.child1", true, Feature::WEB_PAGE_CONTEXT,
GURL("http://foo.com") },
{ "parent1.child2", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent1.child2", false, Feature::WEB_PAGE_CONTEXT,
GURL("http://foo.com") },
{ "parent2", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent2", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent2", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent2.child3", false, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent2.child3", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent2.child3", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent2.child3.child.child", true, Feature::CONTENT_SCRIPT_CONTEXT,
GURL() },
{ "parent2.child3.child.child", false, Feature::BLESSED_EXTENSION_CONTEXT,
GURL() },
{ "parent2.child3.child.child", true, Feature::UNBLESSED_EXTENSION_CONTEXT,
GURL() },
{ "parent3", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent3", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent3", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent3.noparent", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent3.noparent", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent3.noparent", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
{ "parent3.noparent.child", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
{ "parent3.noparent.child", true, Feature::BLESSED_EXTENSION_CONTEXT,
GURL() },
{ "parent3.noparent.child", true, Feature::UNBLESSED_EXTENSION_CONTEXT,
GURL() }
};
base::FilePath api_features_path;
PathService::Get(chrome::DIR_TEST_DATA, &api_features_path);
api_features_path = api_features_path.AppendASCII("extensions")
.AppendASCII("extension_api_unittest")
.AppendASCII("api_features.json");
std::string api_features_str;
ASSERT_TRUE(file_util::ReadFileToString(
api_features_path, &api_features_str)) << "api_features.json";
scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>(
base::JSONReader::Read(api_features_str)));
BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature);
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
ExtensionAPI api;
api.RegisterDependencyProvider("api", &api_feature_provider);
for (base::DictionaryValue::Iterator iter(*value); !iter.IsAtEnd();
iter.Advance()) {
if (iter.key().find(".") == std::string::npos)
api.RegisterSchemaResource(iter.key(), 0);
}
EXPECT_EQ(test_data[i].expect_is_available,
api.IsAvailable(test_data[i].api_full_name,
NULL,
test_data[i].context,
test_data[i].url).is_available()) << i;
}
}
TEST(ExtensionAPITest, IsAnyFeatureAvailableToContext) {
scoped_refptr<const Extension> app = ExtensionBuilder()
.SetManifest(DictionaryBuilder()
.Set("name", "app")
.Set("app", DictionaryBuilder()
.Set("background", DictionaryBuilder()
.Set("scripts", ListBuilder().Append("background.js"))))
.Set("version", "1")
.Set("manifest_version", 2)).Build();
scoped_refptr<const Extension> extension = ExtensionBuilder()
.SetManifest(DictionaryBuilder()
.Set("name", "extension")
.Set("version", "1")
.Set("manifest_version", 2)).Build();
struct {
std::string api_full_name;
bool expect_is_available;
Feature::Context context;
const Extension* extension;
GURL url;
} test_data[] = {
{ "test1", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL() },
{ "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, NULL, GURL() },
{ "test1", false, Feature::UNBLESSED_EXTENSION_CONTEXT, app.get(), GURL() },
{ "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(),
GURL() },
{ "test2", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() },
{ "test2", true, Feature::WEB_PAGE_CONTEXT, NULL,
GURL("http://google.com") },
{ "test2.foo", false, Feature::WEB_PAGE_CONTEXT, NULL,
GURL("http://google.com") },
{ "test3", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() },
{ "test3", true, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://foo.com") },
{ "test4.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() },
{ "test7", false, Feature::WEB_PAGE_CONTEXT, NULL,
GURL("http://google.com") },
{ "test7", true, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://foo.com") },
{ "test7", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://bar.com") }
};
base::FilePath api_features_path;
PathService::Get(chrome::DIR_TEST_DATA, &api_features_path);
api_features_path = api_features_path.AppendASCII("extensions")
.AppendASCII("extension_api_unittest")
.AppendASCII("api_features.json");
std::string api_features_str;
ASSERT_TRUE(file_util::ReadFileToString(
api_features_path, &api_features_str)) << "api_features.json";
scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>(
base::JSONReader::Read(api_features_str)));
BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature);
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
ExtensionAPI api;
api.RegisterDependencyProvider("api", &api_feature_provider);
for (base::DictionaryValue::Iterator iter(*value); !iter.IsAtEnd();
iter.Advance()) {
if (iter.key().find(".") == std::string::npos)
api.RegisterSchemaResource(iter.key(), 0);
}
EXPECT_EQ(test_data[i].expect_is_available,
api.IsAnyFeatureAvailableToContext(test_data[i].api_full_name,
test_data[i].extension,
test_data[i].context,
test_data[i].url)) << i;
}
}
TEST(ExtensionAPITest, LazyGetSchema) {
scoped_ptr<ExtensionAPI> apis(ExtensionAPI::CreateWithDefaultConfiguration());
EXPECT_EQ(NULL, apis->GetSchema(std::string()));
EXPECT_EQ(NULL, apis->GetSchema(std::string()));
EXPECT_EQ(NULL, apis->GetSchema("experimental"));
EXPECT_EQ(NULL, apis->GetSchema("experimental"));
EXPECT_EQ(NULL, apis->GetSchema("foo"));
EXPECT_EQ(NULL, apis->GetSchema("foo"));
EXPECT_TRUE(apis->GetSchema("experimental.dns"));
EXPECT_TRUE(apis->GetSchema("experimental.dns"));
EXPECT_TRUE(apis->GetSchema("extension"));
EXPECT_TRUE(apis->GetSchema("extension"));
EXPECT_TRUE(apis->GetSchema("infobars"));
EXPECT_TRUE(apis->GetSchema("infobars"));
EXPECT_TRUE(apis->GetSchema("omnibox"));
EXPECT_TRUE(apis->GetSchema("omnibox"));
EXPECT_TRUE(apis->GetSchema("storage"));
EXPECT_TRUE(apis->GetSchema("storage"));
}
scoped_refptr<Extension> CreateExtensionWithPermissions(
const std::set<std::string>& permissions) {
base::DictionaryValue manifest;
manifest.SetString("name", "extension");
manifest.SetString("version", "1.0");
manifest.SetInteger("manifest_version", 2);
{
scoped_ptr<base::ListValue> permissions_list(new base::ListValue());
for (std::set<std::string>::const_iterator i = permissions.begin();
i != permissions.end(); ++i) {
permissions_list->Append(new base::StringValue(*i));
}
manifest.Set("permissions", permissions_list.release());
}
std::string error;
scoped_refptr<Extension> extension(Extension::Create(
base::FilePath(), Manifest::UNPACKED,
manifest, Extension::NO_FLAGS, &error));
CHECK(extension.get());
CHECK(error.empty());
return extension;
}
scoped_refptr<Extension> CreateExtensionWithPermission(
const std::string& permission) {
std::set<std::string> permissions;
permissions.insert(permission);
return CreateExtensionWithPermissions(permissions);
}
TEST(ExtensionAPITest, ExtensionWithUnprivilegedAPIs) {
scoped_refptr<Extension> extension;
{
std::set<std::string> permissions;
permissions.insert("storage");
permissions.insert("history");
extension = CreateExtensionWithPermissions(permissions);
}
scoped_ptr<ExtensionAPI> extension_api(
ExtensionAPI::CreateWithDefaultConfiguration());
// "runtime" has privileged parts that should not be accessed by content
// scripts.
EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext(
"runtime.getBackgroundPage",
NULL,
Feature::CONTENT_SCRIPT_CONTEXT,
GURL()));
EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext(
"runtime.sendNativeMessage",
NULL,
Feature::CONTENT_SCRIPT_CONTEXT,
GURL()));
// "runtime" also has unprivileged parts.
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"runtime.sendMessage",
NULL,
Feature::CONTENT_SCRIPT_CONTEXT,
GURL()));
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"runtime.id",
NULL,
Feature::CONTENT_SCRIPT_CONTEXT,
GURL()));
// "storage" is completely unprivileged.
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"storage",
NULL,
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()));
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"storage",
NULL,
Feature::UNBLESSED_EXTENSION_CONTEXT,
GURL()));
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"storage",
NULL,
Feature::CONTENT_SCRIPT_CONTEXT,
GURL()));
// "extension" is partially unprivileged.
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"extension",
NULL,
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()));
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"extension",
NULL,
Feature::UNBLESSED_EXTENSION_CONTEXT,
GURL()));
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"extension",
NULL,
Feature::CONTENT_SCRIPT_CONTEXT,
GURL()));
// "history" is entirely privileged.
EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
"history",
NULL,
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()));
EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext(
"history",
NULL,
Feature::UNBLESSED_EXTENSION_CONTEXT,
GURL()));
EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext(
"history",
NULL,
Feature::CONTENT_SCRIPT_CONTEXT,
GURL()));
}
scoped_refptr<Extension> CreateHostedApp() {
base::DictionaryValue values;
values.SetString(manifest_keys::kName, "test");
values.SetString(manifest_keys::kVersion, "0.1");
values.Set(manifest_keys::kWebURLs, new base::ListValue());
values.SetString(manifest_keys::kLaunchWebURL,
"http://www.example.com");
std::string error;
scoped_refptr<Extension> extension(Extension::Create(
base::FilePath(), Manifest::INTERNAL, values, Extension::NO_FLAGS,
&error));
CHECK(extension.get());
return extension;
}
scoped_refptr<Extension> CreatePackagedAppWithPermissions(
const std::set<std::string>& permissions) {
base::DictionaryValue values;
values.SetString(manifest_keys::kName, "test");
values.SetString(manifest_keys::kVersion, "0.1");
values.SetString(manifest_keys::kPlatformAppBackground,
"http://www.example.com");
base::DictionaryValue* app = new base::DictionaryValue();
base::DictionaryValue* background = new base::DictionaryValue();
base::ListValue* scripts = new base::ListValue();
scripts->Append(new base::StringValue("test.js"));
background->Set("scripts", scripts);
app->Set("background", background);
values.Set(manifest_keys::kApp, app);
{
scoped_ptr<base::ListValue> permissions_list(new base::ListValue());
for (std::set<std::string>::const_iterator i = permissions.begin();
i != permissions.end(); ++i) {
permissions_list->Append(new base::StringValue(*i));
}
values.Set("permissions", permissions_list.release());
}
std::string error;
scoped_refptr<Extension> extension(Extension::Create(
base::FilePath(), Manifest::INTERNAL, values, Extension::NO_FLAGS,
&error));
CHECK(extension.get()) << error;
return extension;
}
TEST(ExtensionAPITest, HostedAppPermissions) {
scoped_refptr<Extension> extension = CreateHostedApp();
scoped_ptr<ExtensionAPI> extension_api(
ExtensionAPI::CreateWithDefaultConfiguration());
// "runtime" and "tabs" should not be available in hosted apps.
EXPECT_FALSE(extension_api->IsAvailable("runtime",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
EXPECT_FALSE(extension_api->IsAvailable("runtime.id",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
EXPECT_FALSE(extension_api->IsAvailable("runtime.sendMessage",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
EXPECT_FALSE(extension_api->IsAvailable("runtime.sendNativeMessage",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
EXPECT_FALSE(extension_api->IsAvailable("tabs.create",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
}
TEST(ExtensionAPITest, AppAndFriendsAvailability) {
scoped_ptr<ExtensionAPI> extension_api(
ExtensionAPI::CreateWithDefaultConfiguration());
// Make sure chrome.app.runtime and chrome.app.window are available to apps,
// and chrome.app is not.
{
std::set<std::string> permissions;
permissions.insert("app.runtime");
permissions.insert("app.window");
scoped_refptr<Extension> extension =
CreatePackagedAppWithPermissions(permissions);
EXPECT_FALSE(extension_api->IsAvailable(
"app",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://foo.com")).is_available());
EXPECT_TRUE(extension_api->IsAvailable(
"app.runtime",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://foo.com")).is_available());
EXPECT_TRUE(extension_api->IsAvailable(
"app.window",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://foo.com")).is_available());
}
// Make sure chrome.app.runtime and chrome.app.window are not available to
// extensions, and chrome.app is.
{
std::set<std::string> permissions;
scoped_refptr<Extension> extension =
CreateExtensionWithPermissions(permissions);
EXPECT_TRUE(extension_api->IsAvailable(
"app",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://foo.com")).is_available());
EXPECT_FALSE(extension_api->IsAvailable(
"app.runtime",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://foo.com")).is_available());
EXPECT_FALSE(extension_api->IsAvailable(
"app.window",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL("http://foo.com")).is_available());
}
}
TEST(ExtensionAPITest, ExtensionWithDependencies) {
// Extension with the "ttsEngine" permission but not the "tts" permission; it
// should not automatically get "tts" permission.
{
scoped_refptr<Extension> extension =
CreateExtensionWithPermission("ttsEngine");
scoped_ptr<ExtensionAPI> api(
ExtensionAPI::CreateWithDefaultConfiguration());
EXPECT_TRUE(api->IsAvailable("ttsEngine",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
EXPECT_FALSE(api->IsAvailable("tts",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
}
// Conversely, extension with the "tts" permission but not the "ttsEngine"
// permission shouldn't get the "ttsEngine" permission.
{
scoped_refptr<Extension> extension =
CreateExtensionWithPermission("tts");
scoped_ptr<ExtensionAPI> api(
ExtensionAPI::CreateWithDefaultConfiguration());
EXPECT_FALSE(api->IsAvailable("ttsEngine",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
EXPECT_TRUE(api->IsAvailable("tts",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
}
}
bool MatchesURL(
ExtensionAPI* api, const std::string& api_name, const std::string& url) {
return api->IsAvailable(
api_name, NULL, Feature::WEB_PAGE_CONTEXT, GURL(url)).is_available();
}
TEST(ExtensionAPITest, URLMatching) {
scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
// "app" API is available to all URLs that content scripts can be injected.
EXPECT_TRUE(MatchesURL(api.get(), "app", "http://example.com/example.html"));
EXPECT_TRUE(MatchesURL(api.get(), "app", "https://blah.net"));
EXPECT_TRUE(MatchesURL(api.get(), "app", "file://somefile.html"));
// But not internal URLs.
EXPECT_FALSE(MatchesURL(api.get(), "app", "about:flags"));
EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome://flags"));
// "app" should be available to chrome-extension URLs.
EXPECT_TRUE(MatchesURL(api.get(), "app",
"chrome-extension://fakeextension"));
// "storage" API (for example) isn't available to any URLs.
EXPECT_FALSE(MatchesURL(api.get(), "storage",
"http://example.com/example.html"));
EXPECT_FALSE(MatchesURL(api.get(), "storage", "https://blah.net"));
EXPECT_FALSE(MatchesURL(api.get(), "storage", "file://somefile.html"));
EXPECT_FALSE(MatchesURL(api.get(), "storage", "about:flags"));
EXPECT_FALSE(MatchesURL(api.get(), "storage", "chrome://flags"));
EXPECT_FALSE(MatchesURL(api.get(), "storage",
"chrome-extension://fakeextension"));
}
TEST(ExtensionAPITest, GetAPINameFromFullName) {
struct {
std::string input;
std::string api_name;
std::string child_name;
} test_data[] = {
{ "", "", "" },
{ "unknown", "", "" },
{ "bookmarks", "bookmarks", "" },
{ "bookmarks.", "bookmarks", "" },
{ ".bookmarks", "", "" },
{ "bookmarks.create", "bookmarks", "create" },
{ "bookmarks.create.", "bookmarks", "create." },
{ "bookmarks.create.monkey", "bookmarks", "create.monkey" },
{ "bookmarkManagerPrivate", "bookmarkManagerPrivate", "" },
{ "bookmarkManagerPrivate.copy", "bookmarkManagerPrivate", "copy" }
};
scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
std::string child_name;
std::string api_name = api->GetAPINameFromFullName(test_data[i].input,
&child_name);
EXPECT_EQ(test_data[i].api_name, api_name) << test_data[i].input;
EXPECT_EQ(test_data[i].child_name, child_name) << test_data[i].input;
}
}
TEST(ExtensionAPITest, DefaultConfigurationFeatures) {
scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
SimpleFeature* bookmarks = static_cast<SimpleFeature*>(
api->GetFeatureDependency("api:bookmarks"));
SimpleFeature* bookmarks_create = static_cast<SimpleFeature*>(
api->GetFeatureDependency("api:bookmarks.create"));
struct {
SimpleFeature* feature;
// TODO(aa): More stuff to test over time.
} test_data[] = {
{ bookmarks },
{ bookmarks_create }
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
SimpleFeature* feature = test_data[i].feature;
ASSERT_TRUE(feature) << i;
EXPECT_TRUE(feature->whitelist()->empty());
EXPECT_TRUE(feature->extension_types()->empty());
EXPECT_EQ(1u, feature->GetContexts()->size());
EXPECT_TRUE(feature->GetContexts()->count(
Feature::BLESSED_EXTENSION_CONTEXT));
EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location());
EXPECT_EQ(Feature::UNSPECIFIED_PLATFORM, feature->platform());
EXPECT_EQ(0, feature->min_manifest_version());
EXPECT_EQ(0, feature->max_manifest_version());
}
}
TEST(ExtensionAPITest, FeaturesRequireContexts) {
// TODO(cduvall): Make this check API featues.
scoped_ptr<base::DictionaryValue> api_features1(new base::DictionaryValue());
scoped_ptr<base::DictionaryValue> api_features2(new base::DictionaryValue());
base::DictionaryValue* test1 = new base::DictionaryValue();
base::DictionaryValue* test2 = new base::DictionaryValue();
base::ListValue* contexts = new base::ListValue();
contexts->Append(new base::StringValue("content_script"));
test1->Set("contexts", contexts);
test1->SetString("channel", "stable");
test2->SetString("channel", "stable");
api_features1->Set("test", test1);
api_features2->Set("test", test2);
struct {
base::DictionaryValue* api_features;
bool expect_success;
} test_data[] = {
{ api_features1.get(), true },
{ api_features2.get(), false }
};
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
BaseFeatureProvider api_feature_provider(*test_data[i].api_features,
CreateAPIFeature);
Feature* feature = api_feature_provider.GetFeature("test");
EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i;
}
}
static void GetDictionaryFromList(const base::DictionaryValue* schema,
const std::string& list_name,
const int list_index,
const base::DictionaryValue** out) {
const base::ListValue* list;
EXPECT_TRUE(schema->GetList(list_name, &list));
EXPECT_TRUE(list->GetDictionary(list_index, out));
}
TEST(ExtensionAPITest, TypesHaveNamespace) {
base::FilePath manifest_path;
PathService::Get(chrome::DIR_TEST_DATA, &manifest_path);
manifest_path = manifest_path.AppendASCII("extensions")
.AppendASCII("extension_api_unittest")
.AppendASCII("types_have_namespace.json");
std::string manifest_str;
ASSERT_TRUE(file_util::ReadFileToString(manifest_path, &manifest_str))
<< "Failed to load: " << manifest_path.value();
ExtensionAPI api;
api.RegisterSchemaResource("test.foo", 0);
api.LoadSchema("test.foo", manifest_str);
const base::DictionaryValue* schema = api.GetSchema("test.foo");
const base::DictionaryValue* dict;
const base::DictionaryValue* sub_dict;
std::string type;
GetDictionaryFromList(schema, "types", 0, &dict);
EXPECT_TRUE(dict->GetString("id", &type));
EXPECT_EQ("test.foo.TestType", type);
EXPECT_TRUE(dict->GetString("customBindings", &type));
EXPECT_EQ("test.foo.TestType", type);
EXPECT_TRUE(dict->GetDictionary("properties", &sub_dict));
const base::DictionaryValue* property;
EXPECT_TRUE(sub_dict->GetDictionary("foo", &property));
EXPECT_TRUE(property->GetString("$ref", &type));
EXPECT_EQ("test.foo.OtherType", type);
EXPECT_TRUE(sub_dict->GetDictionary("bar", &property));
EXPECT_TRUE(property->GetString("$ref", &type));
EXPECT_EQ("fully.qualified.Type", type);
GetDictionaryFromList(schema, "functions", 0, &dict);
GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
EXPECT_TRUE(sub_dict->GetString("$ref", &type));
EXPECT_EQ("test.foo.TestType", type);
EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict));
EXPECT_TRUE(sub_dict->GetString("$ref", &type));
EXPECT_EQ("fully.qualified.Type", type);
GetDictionaryFromList(schema, "functions", 1, &dict);
GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
EXPECT_TRUE(sub_dict->GetString("$ref", &type));
EXPECT_EQ("fully.qualified.Type", type);
EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict));
EXPECT_TRUE(sub_dict->GetString("$ref", &type));
EXPECT_EQ("test.foo.TestType", type);
GetDictionaryFromList(schema, "events", 0, &dict);
GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
EXPECT_TRUE(sub_dict->GetString("$ref", &type));
EXPECT_EQ("test.foo.TestType", type);
GetDictionaryFromList(dict, "parameters", 1, &sub_dict);
EXPECT_TRUE(sub_dict->GetString("$ref", &type));
EXPECT_EQ("fully.qualified.Type", type);
}
// Tests API availability with an empty manifest.
TEST(ExtensionAPITest, NoPermissions) {
const struct {
const char* permission_name;
bool expect_success;
} kTests[] = {
// Test default module/package permission.
{ "extension", true },
{ "i18n", true },
{ "permissions", true },
{ "runtime", true },
{ "test", true },
// These require manifest keys.
{ "browserAction", false },
{ "pageAction", false },
{ "pageActions", false },
// Some negative tests.
{ "bookmarks", false },
{ "cookies", false },
{ "history", false },
// Make sure we find the module name after stripping '.'
{ "runtime.abcd.onStartup", true },
// Test Tabs/Windows functions.
{ "tabs.create", true },
{ "tabs.duplicate", true },
{ "tabs.onRemoved", true },
{ "tabs.remove", true },
{ "tabs.update", true },
{ "tabs.getSelected", true },
{ "tabs.onUpdated", true },
{ "windows.get", true },
{ "windows.create", true },
{ "windows.remove", true },
{ "windows.update", true },
// Test some whitelisted functions. These require no permissions.
{ "app.getDetails", true },
{ "app.getDetailsForFrame", true },
{ "app.getIsInstalled", true },
{ "app.installState", true },
{ "app.runningState", true },
{ "management.getPermissionWarningsByManifest", true },
{ "management.uninstallSelf", true },
// But other functions in those modules do.
{ "management.getPermissionWarningsById", false },
};
scoped_ptr<ExtensionAPI> extension_api(
ExtensionAPI::CreateWithDefaultConfiguration());
scoped_refptr<Extension> extension =
BuildExtension(ExtensionBuilder().Pass()).Build();
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
EXPECT_EQ(kTests[i].expect_success,
extension_api->IsAvailable(kTests[i].permission_name,
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available())
<< "Permission being tested: " << kTests[i].permission_name;
}
}
// Tests that permissions that require manifest keys are available when those
// keys are present.
TEST(ExtensionAPITest, ManifestKeys) {
scoped_ptr<ExtensionAPI> extension_api(
ExtensionAPI::CreateWithDefaultConfiguration());
scoped_refptr<Extension> extension =
BuildExtension(ExtensionBuilder().Pass())
.MergeManifest(DictionaryBuilder().Set("browser_action",
DictionaryBuilder().Pass()))
.Build();
EXPECT_TRUE(extension_api->IsAvailable("browserAction",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
EXPECT_FALSE(extension_api->IsAvailable("pageAction",
extension.get(),
Feature::BLESSED_EXTENSION_CONTEXT,
GURL()).is_available());
}
} // namespace extensions