# Copyright 2013 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. import posixpath from compiled_file_system import Cache, SingleFile, Unicode from extensions_paths import API_PATHS from features_bundle import HasParent, GetParentName from file_system import FileNotFoundError from future import All, Future, Race from operator import itemgetter from path_util import Join from platform_util import PlatformToExtensionType from schema_processor import SchemaProcessor, SchemaProcessorFactory from third_party.json_schema_compiler.json_schema import DeleteNodes from third_party.json_schema_compiler.model import Namespace, UnixName def GetNodeCategories(): '''Returns a tuple of the possible categories a node may belong to. ''' return ('types', 'functions', 'events', 'properties') class ContentScriptAPI(object): '''Represents an API available to content scripts. |name| is the name of the API or API node this object represents. |restrictedTo| is a list of dictionaries representing the nodes of this API that are available to content scripts, or None if the entire API is available to content scripts. ''' def __init__(self, name): self.name = name self.restrictedTo = None def __eq__(self, o): return self.name == o.name and self.restrictedTo == o.restrictedTo def __ne__(self, o): return not (self == o) def __repr__(self): return '' % (name, restrictedTo) def __str__(self): return repr(self) class APIModels(object): '''Tracks APIs and their Models. ''' def __init__(self, features_bundle, compiled_fs_factory, file_system, object_store_creator, platform, schema_processor_factory): self._features_bundle = features_bundle self._platform = PlatformToExtensionType(platform) self._model_cache = compiled_fs_factory.Create( file_system, self._CreateAPIModel, APIModels, category=self._platform) self._object_store = object_store_creator.Create(APIModels) self._schema_processor = Future(callback=lambda: schema_processor_factory.Create(False)) @Cache @SingleFile @Unicode def _CreateAPIModel(self, path, data): def does_not_include_platform(node): return ('extension_types' in node and node['extension_types'] != 'all' and self._platform not in node['extension_types']) schema = self._schema_processor.Get().Process(path, data)[0] if not schema: raise ValueError('No schema for %s' % path) return Namespace(DeleteNodes( schema, matcher=does_not_include_platform), path) def GetNames(self): # API names appear alongside some of their methods/events/etc in the # features file. APIs are those which either implicitly or explicitly have # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are # APIs; runtime.onConnectNative is not). api_features = self._features_bundle.GetAPIFeatures().Get() return [name for name, feature in api_features.iteritems() if not HasParent(name, feature, api_features)] def _GetPotentialPathsForModel(self, api_name): '''Returns the list of file system paths that the model for |api_name| might be located at. ''' # By default |api_name| is assumed to be given without a path or extension, # so combinations of known paths and extension types will be searched. api_extensions = ('.json', '.idl') api_paths = API_PATHS # Callers sometimes include a file extension and/or prefix path with the # |api_name| argument. We believe them and narrow the search space # accordingly. name, ext = posixpath.splitext(api_name) if ext in api_extensions: api_extensions = (ext,) api_name = name for api_path in api_paths: if api_name.startswith(api_path): api_name = api_name[len(api_path):] api_paths = (api_path,) break # API names are given as declarativeContent and app.window but file names # will be declarative_content and app_window. file_name = UnixName(api_name).replace('.', '_') # Devtools APIs are in API/devtools/ not API/, and have their # "devtools" names removed from the file names. basename = posixpath.basename(file_name) if 'devtools_' in basename: file_name = posixpath.join( 'devtools', file_name.replace(basename, basename.replace('devtools_' , ''))) return [Join(path, file_name + ext) for ext in api_extensions for path in api_paths] def GetModel(self, api_name): futures = [self._model_cache.GetFromFile(path) for path in self._GetPotentialPathsForModel(api_name)] return Race(futures, except_pass=(FileNotFoundError, ValueError)) def GetContentScriptAPIs(self): '''Creates a dict of APIs and nodes supported by content scripts in this format: { 'extension': '', ... } ''' content_script_apis_future = self._object_store.Get('content_script_apis') api_features_future = self._features_bundle.GetAPIFeatures() def resolve(): content_script_apis = content_script_apis_future.Get() if content_script_apis is not None: return content_script_apis api_features = api_features_future.Get() content_script_apis = {} for name, feature in api_features.iteritems(): if 'content_script' not in feature.get('contexts', ()): continue parent = GetParentName(name, feature, api_features) if parent is None: content_script_apis[name] = ContentScriptAPI(name) else: # Creates a dict for the individual node. node = {'node': name[len(parent) + 1:]} if parent not in content_script_apis: content_script_apis[parent] = ContentScriptAPI(parent) if content_script_apis[parent].restrictedTo: content_script_apis[parent].restrictedTo.append(node) else: content_script_apis[parent].restrictedTo = [node] self._object_store.Set('content_script_apis', content_script_apis) return content_script_apis return Future(callback=resolve) def Refresh(self): futures = [self.GetModel(name) for name in self.GetNames()] return All(futures, except_pass=(FileNotFoundError, ValueError)) def IterModels(self): future_models = [(name, self.GetModel(name)) for name in self.GetNames()] for name, future_model in future_models: try: model = future_model.Get() except FileNotFoundError: continue if model: yield name, model