// Copyright 2017 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "extensions/renderer/feature_cache.h" #include #include "base/command_line.h" #include "content/public/common/content_switches.h" #include "extensions/common/extension.h" #include "extensions/common/extension_api.h" #include "extensions/common/features/feature_developer_mode_only.h" #include "extensions/common/features/feature_provider.h" #include "extensions/renderer/dispatcher.h" namespace extensions { FeatureCache::ExtensionFeatureData::ExtensionFeatureData() = default; FeatureCache::ExtensionFeatureData::ExtensionFeatureData( const ExtensionFeatureData&) = default; FeatureCache::ExtensionFeatureData::~ExtensionFeatureData() = default; FeatureCache::FeatureCache() = default; FeatureCache::~FeatureCache() = default; FeatureCache::FeatureNameVector FeatureCache::GetAvailableFeatures( Feature::Context context_type, const Extension* extension, const GURL& url) { bool is_webui_or_untrusted_webui = context_type == Feature::WEBUI_CONTEXT || context_type == Feature::WEBUI_UNTRUSTED_CONTEXT; DCHECK_NE(is_webui_or_untrusted_webui, !!extension) << "WebUI contexts shouldn't have extensions."; DCHECK_NE(Feature::WEB_PAGE_CONTEXT, context_type) << "FeatureCache shouldn't be used for web contexts."; DCHECK_NE(Feature::UNSPECIFIED_CONTEXT, context_type) << "FeatureCache shouldn't be used for unspecified contexts."; const ExtensionFeatureData& features = GetFeaturesFromCache(context_type, extension, url.DeprecatedGetOriginAsURL(), kRendererProfileId); FeatureNameVector names; names.reserve(features.available_features.size()); for (const Feature* feature : features.available_features) { // Since we only cache based on extension id and context type, instead of // all attributes of a context (like URL), we need to double-check if the // feature is actually available to the context. This is still a win, since // we only perform this check on the (much smaller) set of features that // *may* be available, rather than all known features. // TODO(devlin): Optimize this - we should be able to tell if a feature may // change based on additional context attributes. if (ExtensionAPI::GetSharedInstance()->IsAnyFeatureAvailableToContext( *feature, extension, context_type, url, CheckAliasStatus::NOT_ALLOWED, kRendererProfileId)) { names.push_back(feature->name()); } } return names; } FeatureCache::FeatureNameVector FeatureCache::GetDeveloperModeRestrictedFeatures(Feature::Context context_type, const Extension* extension, const GURL& url) { const ExtensionFeatureData& features = GetFeaturesFromCache(context_type, extension, url.DeprecatedGetOriginAsURL(), kRendererProfileId); FeatureNameVector names; names.reserve(features.dev_mode_restricted_features.size()); for (const Feature* feature : features.dev_mode_restricted_features) { names.push_back(feature->name()); } return names; } void FeatureCache::InvalidateExtension(const ExtensionId& extension_id) { for (auto iter = extension_cache_.begin(); iter != extension_cache_.end();) { if (iter->first.first == extension_id) iter = extension_cache_.erase(iter); else ++iter; } } const FeatureCache::ExtensionFeatureData& FeatureCache::GetFeaturesFromCache( Feature::Context context_type, const Extension* extension, const GURL& origin, int context_id) { if (context_type == Feature::WEBUI_CONTEXT || context_type == Feature::WEBUI_UNTRUSTED_CONTEXT) { auto iter = webui_cache_.find(origin); if (iter != webui_cache_.end()) return iter->second; return webui_cache_ .emplace(origin, CreateCacheEntry(context_type, extension, origin, context_id)) .first->second; } DCHECK(extension); ExtensionCacheMapKey key(extension->id(), context_type); auto iter = extension_cache_.find(key); if (iter != extension_cache_.end()) return iter->second; return extension_cache_ .emplace(key, CreateCacheEntry(context_type, extension, origin, context_id)) .first->second; } FeatureCache::ExtensionFeatureData FeatureCache::CreateCacheEntry( Feature::Context context_type, const Extension* extension, const GURL& origin, int context_id) { ExtensionFeatureData features; const FeatureProvider* api_feature_provider = FeatureProvider::GetAPIFeatures(); GURL empty_url; // We ignore the URL if this is an extension context in order to maximize // cache hits. For WebUI and untrusted WebUI, we key on origin. // Note: Currently, we only ever have matches based on origin, so this is // okay. If this changes, we'll have to get more creative about our WebUI // caching. const bool should_use_url = (context_type == Feature::WEBUI_CONTEXT || context_type == Feature::WEBUI_UNTRUSTED_CONTEXT); const GURL& url_to_use = should_use_url ? origin : empty_url; for (const auto& map_entry : api_feature_provider->GetAllFeatures()) { const Feature* feature = map_entry.second.get(); // Exclude internal APIs. if (feature->IsInternal()) continue; // Exclude child features (like events or specific functions). // TODO(devlin): Optimize this - instead of skipping child features and then // checking IsAnyFeatureAvailableToContext() (which checks child features), // we should just check all features directly. if (api_feature_provider->GetParent(*feature) != nullptr) continue; // Skip chrome.test if this isn't a test. if (map_entry.first == "test" && !base::CommandLine::ForCurrentProcess()->HasSwitch( ::switches::kTestType)) { continue; } if (!ExtensionAPI::GetSharedInstance()->IsAnyFeatureAvailableToContext( *feature, extension, context_type, url_to_use, CheckAliasStatus::NOT_ALLOWED, context_id)) { if (feature ->IsAvailableToContextIgnoringDevMode( extension, context_type, url_to_use, Feature::GetCurrentPlatform(), context_id) .is_available()) { features.dev_mode_restricted_features.push_back(feature); } continue; } features.available_features.push_back(feature); } std::sort( features.dev_mode_restricted_features.begin(), features.dev_mode_restricted_features.end(), [](const Feature* a, const Feature* b) { return a->name() < b->name(); }); std::sort( features.available_features.begin(), features.available_features.end(), [](const Feature* a, const Feature* b) { return a->name() < b->name(); }); DCHECK(std::unique(features.dev_mode_restricted_features.begin(), features.dev_mode_restricted_features.end()) == features.dev_mode_restricted_features.end()); DCHECK(std::unique(features.available_features.begin(), features.available_features.end()) == features.available_features.end()); return features; } } // namespace extensions