1# Copyright 2013 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import posixpath 6 7from compiled_file_system import Cache, SingleFile, Unicode 8from extensions_paths import API_PATHS 9from features_bundle import HasParent, GetParentName 10from file_system import FileNotFoundError 11from future import All, Future, Race 12from operator import itemgetter 13from path_util import Join 14from platform_util import PlatformToExtensionType 15from schema_processor import SchemaProcessor, SchemaProcessorFactory 16from third_party.json_schema_compiler.json_schema import DeleteNodes 17from third_party.json_schema_compiler.model import Namespace, UnixName 18 19 20def GetNodeCategories(): 21 '''Returns a tuple of the possible categories a node may belong to. 22 ''' 23 return ('types', 'functions', 'events', 'properties') 24 25 26class ContentScriptAPI(object): 27 '''Represents an API available to content scripts. 28 29 |name| is the name of the API or API node this object represents. 30 |restrictedTo| is a list of dictionaries representing the nodes 31 of this API that are available to content scripts, or None if the 32 entire API is available to content scripts. 33 ''' 34 def __init__(self, name): 35 self.name = name 36 self.restrictedTo = None 37 38 def __eq__(self, o): 39 return self.name == o.name and self.restrictedTo == o.restrictedTo 40 41 def __ne__(self, o): 42 return not (self == o) 43 44 def __repr__(self): 45 return '<ContentScriptAPI name=%s, restrictedTo=%s>' % (name, restrictedTo) 46 47 def __str__(self): 48 return repr(self) 49 50 51class APIModels(object): 52 '''Tracks APIs and their Models. 53 ''' 54 55 def __init__(self, 56 features_bundle, 57 compiled_fs_factory, 58 file_system, 59 object_store_creator, 60 platform, 61 schema_processor_factory): 62 self._features_bundle = features_bundle 63 self._platform = PlatformToExtensionType(platform) 64 self._model_cache = compiled_fs_factory.Create( 65 file_system, self._CreateAPIModel, APIModels, category=self._platform) 66 self._object_store = object_store_creator.Create(APIModels) 67 self._schema_processor = Future(callback=lambda: 68 schema_processor_factory.Create(False)) 69 70 @Cache 71 @SingleFile 72 @Unicode 73 def _CreateAPIModel(self, path, data): 74 def does_not_include_platform(node): 75 return ('extension_types' in node and 76 node['extension_types'] != 'all' and 77 self._platform not in node['extension_types']) 78 79 schema = self._schema_processor.Get().Process(path, data)[0] 80 if not schema: 81 raise ValueError('No schema for %s' % path) 82 return Namespace(DeleteNodes( 83 schema, matcher=does_not_include_platform), path) 84 85 def GetNames(self): 86 # API names appear alongside some of their methods/events/etc in the 87 # features file. APIs are those which either implicitly or explicitly have 88 # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are 89 # APIs; runtime.onConnectNative is not). 90 api_features = self._features_bundle.GetAPIFeatures().Get() 91 return [name for name, feature in api_features.iteritems() 92 if not HasParent(name, feature, api_features)] 93 94 def _GetPotentialPathsForModel(self, api_name): 95 '''Returns the list of file system paths that the model for |api_name| 96 might be located at. 97 ''' 98 # By default |api_name| is assumed to be given without a path or extension, 99 # so combinations of known paths and extension types will be searched. 100 api_extensions = ('.json', '.idl') 101 api_paths = API_PATHS 102 103 # Callers sometimes include a file extension and/or prefix path with the 104 # |api_name| argument. We believe them and narrow the search space 105 # accordingly. 106 name, ext = posixpath.splitext(api_name) 107 if ext in api_extensions: 108 api_extensions = (ext,) 109 api_name = name 110 for api_path in api_paths: 111 if api_name.startswith(api_path): 112 api_name = api_name[len(api_path):] 113 api_paths = (api_path,) 114 break 115 116 # API names are given as declarativeContent and app.window but file names 117 # will be declarative_content and app_window. 118 file_name = UnixName(api_name).replace('.', '_') 119 # Devtools APIs are in API/devtools/ not API/, and have their 120 # "devtools" names removed from the file names. 121 basename = posixpath.basename(file_name) 122 if 'devtools_' in basename: 123 file_name = posixpath.join( 124 'devtools', file_name.replace(basename, 125 basename.replace('devtools_' , ''))) 126 127 return [Join(path, file_name + ext) for ext in api_extensions 128 for path in api_paths] 129 130 def GetModel(self, api_name): 131 futures = [self._model_cache.GetFromFile(path) 132 for path in self._GetPotentialPathsForModel(api_name)] 133 return Race(futures, except_pass=(FileNotFoundError, ValueError)) 134 135 def GetContentScriptAPIs(self): 136 '''Creates a dict of APIs and nodes supported by content scripts in 137 this format: 138 139 { 140 'extension': '<ContentScriptAPI name='extension', 141 restrictedTo=[{'node': 'onRequest'}]>', 142 ... 143 } 144 ''' 145 content_script_apis_future = self._object_store.Get('content_script_apis') 146 api_features_future = self._features_bundle.GetAPIFeatures() 147 def resolve(): 148 content_script_apis = content_script_apis_future.Get() 149 if content_script_apis is not None: 150 return content_script_apis 151 152 api_features = api_features_future.Get() 153 content_script_apis = {} 154 for name, feature in api_features.iteritems(): 155 if 'content_script' not in feature.get('contexts', ()): 156 continue 157 parent = GetParentName(name, feature, api_features) 158 if parent is None: 159 content_script_apis[name] = ContentScriptAPI(name) 160 else: 161 # Creates a dict for the individual node. 162 node = {'node': name[len(parent) + 1:]} 163 if parent not in content_script_apis: 164 content_script_apis[parent] = ContentScriptAPI(parent) 165 if content_script_apis[parent].restrictedTo: 166 content_script_apis[parent].restrictedTo.append(node) 167 else: 168 content_script_apis[parent].restrictedTo = [node] 169 170 self._object_store.Set('content_script_apis', content_script_apis) 171 return content_script_apis 172 return Future(callback=resolve) 173 174 def Refresh(self): 175 futures = [self.GetModel(name) for name in self.GetNames()] 176 return All(futures, except_pass=(FileNotFoundError, ValueError)) 177 178 def IterModels(self): 179 future_models = [(name, self.GetModel(name)) for name in self.GetNames()] 180 for name, future_model in future_models: 181 try: 182 model = future_model.Get() 183 except FileNotFoundError: 184 continue 185 if model: 186 yield name, model 187