| // 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 "extensions/common/extension_api.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/path_service.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/common/features/api_feature.h" |
| #include "extensions/common/features/base_feature_provider.h" |
| #include "extensions/common/features/simple_feature.h" |
| #include "extensions/common/manifest.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/test_util.h" |
| #include "extensions/common/value_builder.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace extensions { |
| |
| using test_util::BuildExtension; |
| |
| SimpleFeature* CreateAPIFeature() { |
| return new APIFeature(); |
| } |
| |
| TEST(ExtensionAPITest, Creation) { |
| ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance(); |
| EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance()); |
| |
| std::unique_ptr<ExtensionAPI> new_instance( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| EXPECT_NE(new_instance.get(), |
| std::unique_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(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(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, 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(base::ReadFileToString( |
| api_features_path, &api_features_str)) << "api_features.json"; |
| |
| std::unique_ptr<base::DictionaryValue> value( |
| static_cast<base::DictionaryValue*>( |
| base::JSONReader::Read(api_features_str).release())); |
| BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature); |
| |
| for (size_t i = 0; i < arraysize(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); |
| } |
| |
| ExtensionAPI::OverrideSharedInstanceForTest scope(&api); |
| bool expected = test_data[i].expect_is_available; |
| Feature::Availability availability = |
| api.IsAvailable(test_data[i].api_full_name, |
| NULL, |
| test_data[i].context, |
| test_data[i].url); |
| EXPECT_EQ(expected, availability.is_available()) |
| << base::StringPrintf("Test %d: Feature '%s' was %s: %s", |
| static_cast<int>(i), |
| test_data[i].api_full_name.c_str(), |
| expected ? "not available" : "available", |
| availability.message().c_str()); |
| } |
| } |
| |
| 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") |
| .Build()) |
| .Build()) |
| .Build()) |
| .Set("version", "1") |
| .Set("manifest_version", 2) |
| .Build()) |
| .Build(); |
| scoped_refptr<const Extension> extension = |
| ExtensionBuilder() |
| .SetManifest(DictionaryBuilder() |
| .Set("name", "extension") |
| .Set("version", "1") |
| .Set("manifest_version", 2) |
| .Build()) |
| .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(base::ReadFileToString( |
| api_features_path, &api_features_str)) << "api_features.json"; |
| |
| std::unique_ptr<base::DictionaryValue> value( |
| static_cast<base::DictionaryValue*>( |
| base::JSONReader::Read(api_features_str).release())); |
| BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature); |
| |
| for (size_t i = 0; i < arraysize(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); |
| } |
| |
| Feature* test_feature = |
| api_feature_provider.GetFeature(test_data[i].api_full_name); |
| ASSERT_TRUE(test_feature); |
| EXPECT_EQ(test_data[i].expect_is_available, |
| api.IsAnyFeatureAvailableToContext(*test_feature, |
| test_data[i].extension, |
| test_data[i].context, |
| test_data[i].url)) |
| << i; |
| } |
| } |
| |
| TEST(ExtensionAPITest, LazyGetSchema) { |
| std::unique_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("dns")); |
| EXPECT_TRUE(apis->GetSchema("dns")); |
| EXPECT_TRUE(apis->GetSchema("extension")); |
| EXPECT_TRUE(apis->GetSchema("extension")); |
| 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); |
| { |
| std::unique_ptr<base::ListValue> permissions_list(new base::ListValue()); |
| for (std::set<std::string>::const_iterator i = permissions.begin(); |
| i != permissions.end(); ++i) { |
| permissions_list->AppendString(*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); |
| } |
| |
| std::unique_ptr<ExtensionAPI> extension_api( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| |
| const FeatureProvider& api_features = *FeatureProvider::GetAPIFeatures(); |
| |
| // "storage" is completely unprivileged. |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("storage"), |
| NULL, |
| Feature::BLESSED_EXTENSION_CONTEXT, |
| GURL())); |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("storage"), |
| NULL, |
| Feature::UNBLESSED_EXTENSION_CONTEXT, |
| GURL())); |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("storage"), |
| NULL, |
| Feature::CONTENT_SCRIPT_CONTEXT, |
| GURL())); |
| |
| // "extension" is partially unprivileged. |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("extension"), |
| NULL, |
| Feature::BLESSED_EXTENSION_CONTEXT, |
| GURL())); |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("extension"), |
| NULL, |
| Feature::UNBLESSED_EXTENSION_CONTEXT, |
| GURL())); |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("extension"), |
| NULL, |
| Feature::CONTENT_SCRIPT_CONTEXT, |
| GURL())); |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("extension.getURL"), |
| NULL, |
| Feature::CONTENT_SCRIPT_CONTEXT, |
| GURL())); |
| |
| // "history" is entirely privileged. |
| EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("history"), |
| NULL, |
| Feature::BLESSED_EXTENSION_CONTEXT, |
| GURL())); |
| EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("history"), |
| NULL, |
| Feature::UNBLESSED_EXTENSION_CONTEXT, |
| GURL())); |
| EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext( |
| *api_features.GetFeature("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->AppendString("test.js"); |
| background->Set("scripts", scripts); |
| app->Set("background", background); |
| values.Set(manifest_keys::kApp, app); |
| { |
| std::unique_ptr<base::ListValue> permissions_list(new base::ListValue()); |
| for (std::set<std::string>::const_iterator i = permissions.begin(); |
| i != permissions.end(); ++i) { |
| permissions_list->AppendString(*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(); |
| |
| std::unique_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) { |
| std::unique_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"); |
| std::unique_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"); |
| std::unique_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) { |
| std::unique_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")); |
| |
| // Also to internal URLs. |
| EXPECT_TRUE(MatchesURL(api.get(), "app", "about:flags")); |
| EXPECT_TRUE(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" } |
| }; |
| |
| std::unique_ptr<ExtensionAPI> api( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| for (size_t i = 0; i < arraysize(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) { |
| std::unique_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(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(SimpleFeature::UNSPECIFIED_LOCATION, feature->location()); |
| EXPECT_TRUE(feature->platforms()->empty()); |
| EXPECT_EQ(0, feature->min_manifest_version()); |
| EXPECT_EQ(0, feature->max_manifest_version()); |
| } |
| } |
| |
| TEST(ExtensionAPITest, FeaturesRequireContexts) { |
| // TODO(cduvall): Make this check API featues. |
| std::unique_ptr<base::DictionaryValue> api_features1( |
| new base::DictionaryValue()); |
| std::unique_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->AppendString("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(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(base::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.getIsInstalled", true }, |
| { "app.installState", true }, |
| { "app.runningState", true }, |
| { "management.getPermissionWarningsByManifest", true }, |
| { "management.uninstallSelf", true }, |
| // But other functions in those modules do. |
| { "management.getPermissionWarningsById", false }, |
| { "runtime.connectNative", false }, |
| }; |
| |
| std::unique_ptr<ExtensionAPI> extension_api( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| scoped_refptr<Extension> extension = |
| BuildExtension(ExtensionBuilder()).Build(); |
| |
| for (size_t i = 0; i < arraysize(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) { |
| std::unique_ptr<ExtensionAPI> extension_api( |
| ExtensionAPI::CreateWithDefaultConfiguration()); |
| |
| scoped_refptr<Extension> extension = |
| BuildExtension(ExtensionBuilder()) |
| .MergeManifest(DictionaryBuilder() |
| .Set("browser_action", DictionaryBuilder().Build()) |
| .Build()) |
| .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 |