Home | History | Annotate | Download | only in server2
      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 
      5 import posixpath
      6 
      7 from compiled_file_system import Cache, SingleFile, Unicode
      8 from extensions_paths import API_PATHS
      9 from features_bundle import HasParent, GetParentName
     10 from file_system import FileNotFoundError
     11 from future import All, Future, Race
     12 from operator import itemgetter
     13 from path_util import Join
     14 from platform_util import PlatformToExtensionType
     15 from schema_processor import SchemaProcessor, SchemaProcessorFactory
     16 from third_party.json_schema_compiler.json_schema import DeleteNodes
     17 from third_party.json_schema_compiler.model import Namespace, UnixName
     18 
     19 
     20 def GetNodeCategories():
     21   '''Returns a tuple of the possible categories a node may belong to.
     22   '''
     23   return ('types', 'functions', 'events', 'properties')
     24 
     25 
     26 class 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 
     51 class 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