| // Copyright 2017 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/renderer/feature_cache.h" |
| |
| #include <algorithm> |
| |
| #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_provider.h" |
| |
| namespace extensions { |
| |
| FeatureCache::FeatureCache() {} |
| FeatureCache::~FeatureCache() = default; |
| |
| FeatureCache::FeatureNameVector FeatureCache::GetAvailableFeatures( |
| Feature::Context context_type, |
| const Extension* extension, |
| const GURL& url) { |
| DCHECK_NE(context_type == Feature::WEBUI_CONTEXT, !!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 FeatureVector& features = |
| GetFeaturesFromCache(context_type, extension, url.GetOrigin()); |
| FeatureNameVector names; |
| names.reserve(features.size()); |
| for (const Feature* feature : 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)) { |
| 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::FeatureVector& FeatureCache::GetFeaturesFromCache( |
| Feature::Context context_type, |
| const Extension* extension, |
| const GURL& origin) { |
| if (context_type == Feature::WEBUI_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)) |
| .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)) |
| .first->second; |
| } |
| |
| FeatureCache::FeatureVector FeatureCache::CreateCacheEntry( |
| Feature::Context context_type, |
| const Extension* extension, |
| const GURL& origin) { |
| bool is_webui = context_type == Feature::WEBUI_CONTEXT; |
| FeatureVector 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, 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 GURL& url_to_use = is_webui ? 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)) { |
| continue; |
| } |
| |
| features.push_back(feature); |
| } |
| |
| std::sort( |
| features.begin(), features.end(), |
| [](const Feature* a, const Feature* b) { return a->name() < b->name(); }); |
| DCHECK(std::unique(features.begin(), features.end()) == features.end()); |
| |
| return features; |
| } |
| |
| } // namespace extensions |