summaryrefslogtreecommitdiff
path: root/chromium/extensions/renderer/feature_cache.cc
blob: b0533808544526accbc7c8ee6d0f85b3ebe9ef0b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// 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) {
  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 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 ||
      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))
        .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) {
  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 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)) {
      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