11e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
21e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
31e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)# found in the LICENSE file.
41e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
51e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)import posixpath
61e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
71320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccifrom compiled_file_system import Cache, SingleFile, Unicode
8effb81e5f8246d0db0270817048dc992db66e9fbBen Murdochfrom extensions_paths import API_PATHS
95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from features_bundle import HasParent, GetParentName
101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)from file_system import FileNotFoundError
111320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccifrom future import All, Future, Race
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)from operator import itemgetter
131320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccifrom path_util import Join
14116680a4aac90f2aa7413d9095a592090648e557Ben Murdochfrom platform_util import PlatformToExtensionType
151320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccifrom schema_processor import SchemaProcessor, SchemaProcessorFactory
16116680a4aac90f2aa7413d9095a592090648e557Ben Murdochfrom third_party.json_schema_compiler.json_schema import DeleteNodes
171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)from third_party.json_schema_compiler.model import Namespace, UnixName
181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)def GetNodeCategories():
215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  '''Returns a tuple of the possible categories a node may belong to.
225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  '''
235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return ('types', 'functions', 'events', 'properties')
245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)class ContentScriptAPI(object):
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  '''Represents an API available to content scripts.
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  |name| is the name of the API or API node this object represents.
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  |restrictedTo| is a list of dictionaries representing the nodes
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  of this API that are available to content scripts, or None if the
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  entire API is available to content scripts.
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  '''
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def __init__(self, name):
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self.name = name
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self.restrictedTo = None
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def __eq__(self, o):
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return self.name == o.name and self.restrictedTo == o.restrictedTo
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def __ne__(self, o):
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return not (self == o)
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def __repr__(self):
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return '<ContentScriptAPI name=%s, restrictedTo=%s>' % (name, restrictedTo)
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def __str__(self):
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return repr(self)
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)class APIModels(object):
521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  '''Tracks APIs and their Models.
531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  '''
541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
55116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  def __init__(self,
56116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch               features_bundle,
57116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch               compiled_fs_factory,
58116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch               file_system,
595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)               object_store_creator,
601320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci               platform,
611320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci               schema_processor_factory):
621e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._features_bundle = features_bundle
63116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    self._platform = PlatformToExtensionType(platform)
641e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    self._model_cache = compiled_fs_factory.Create(
65116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch        file_system, self._CreateAPIModel, APIModels, category=self._platform)
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    self._object_store = object_store_creator.Create(APIModels)
671320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    self._schema_processor = Future(callback=lambda:
681320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                    schema_processor_factory.Create(False))
69116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
701320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  @Cache
71116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  @SingleFile
72116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  @Unicode
73116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch  def _CreateAPIModel(self, path, data):
74116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    def does_not_include_platform(node):
75116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      return ('extension_types' in node and
76116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch              node['extension_types'] != 'all' and
77116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch              self._platform not in node['extension_types'])
78116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch
791320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    schema = self._schema_processor.Get().Process(path, data)[0]
80116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    if not schema:
81116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch      raise ValueError('No schema for %s' % path)
82116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch    return Namespace(DeleteNodes(
831320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci        schema, matcher=does_not_include_platform), path)
841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)  def GetNames(self):
860f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    # API names appear alongside some of their methods/events/etc in the
870f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    # features file. APIs are those which either implicitly or explicitly have
880f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are
890f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    # APIs; runtime.onConnectNative is not).
90f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    api_features = self._features_bundle.GetAPIFeatures().Get()
910f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)    return [name for name, feature in api_features.iteritems()
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            if not HasParent(name, feature, api_features)]
931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def _GetPotentialPathsForModel(self, api_name):
951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    '''Returns the list of file system paths that the model for |api_name|
961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    might be located at.
971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    '''
98a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # By default |api_name| is assumed to be given without a path or extension,
99a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # so combinations of known paths and extension types will be searched.
100a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    api_extensions = ('.json', '.idl')
101effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch    api_paths = API_PATHS
1021e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
103a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # Callers sometimes include a file extension and/or prefix path with the
104a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # |api_name| argument. We believe them and narrow the search space
105a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    # accordingly.
106a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    name, ext = posixpath.splitext(api_name)
107a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    if ext in api_extensions:
108a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      api_extensions = (ext,)
109a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      api_name = name
110a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)    for api_path in api_paths:
111a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)      if api_name.startswith(api_path):
112a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        api_name = api_name[len(api_path):]
113a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        api_paths = (api_path,)
114a1401311d1ab56c4ed0a474bd38c108f75cb0cd9Torne (Richard Coles)        break
1151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    # API names are given as declarativeContent and app.window but file names
1171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    # will be declarative_content and app_window.
1181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    file_name = UnixName(api_name).replace('.', '_')
119f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    # Devtools APIs are in API/devtools/ not API/, and have their
1201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    # "devtools" names removed from the file names.
1211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)    basename = posixpath.basename(file_name)
122f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    if 'devtools_' in basename:
1231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)      file_name = posixpath.join(
124f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)          'devtools', file_name.replace(basename,
125f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)                                        basename.replace('devtools_' , '')))
1261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)
1271320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return [Join(path, file_name + ext) for ext in api_extensions
1281320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci                                        for path in api_paths]
1291320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
1301320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def GetModel(self, api_name):
1311320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    futures = [self._model_cache.GetFromFile(path)
1321320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci               for path in self._GetPotentialPathsForModel(api_name)]
1331320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return Race(futures, except_pass=(FileNotFoundError, ValueError))
134f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)
1355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  def GetContentScriptAPIs(self):
1365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    '''Creates a dict of APIs and nodes supported by content scripts in
1375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    this format:
1385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      {
1405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        'extension': '<ContentScriptAPI name='extension',
1415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)                                        restrictedTo=[{'node': 'onRequest'}]>',
1425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        ...
1435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      }
1445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    '''
1455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    content_script_apis_future = self._object_store.Get('content_script_apis')
1465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    api_features_future = self._features_bundle.GetAPIFeatures()
1475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    def resolve():
1485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      content_script_apis = content_script_apis_future.Get()
1495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      if content_script_apis is not None:
1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        return content_script_apis
1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      api_features = api_features_future.Get()
1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      content_script_apis = {}
1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      for name, feature in api_features.iteritems():
1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if 'content_script' not in feature.get('contexts', ()):
1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          continue
1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        parent = GetParentName(name, feature, api_features)
1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        if parent is None:
1595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          content_script_apis[name] = ContentScriptAPI(name)
1605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)        else:
1615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          # Creates a dict for the individual node.
1625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          node = {'node': name[len(parent) + 1:]}
1635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          if parent not in content_script_apis:
1645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            content_script_apis[parent] = ContentScriptAPI(parent)
1655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          if content_script_apis[parent].restrictedTo:
1665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            content_script_apis[parent].restrictedTo.append(node)
1675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)          else:
1685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)            content_script_apis[parent].restrictedTo = [node]
1695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      self._object_store.Set('content_script_apis', content_script_apis)
1715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      return content_script_apis
1725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return Future(callback=resolve)
1735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1741320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  def Refresh(self):
1750529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch    futures = [self.GetModel(name) for name in self.GetNames()]
1765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return All(futures, except_pass=(FileNotFoundError, ValueError))
1770529e5d033099cbfc42635f6f6183833b09dff6eBen Murdoch
178f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)  def IterModels(self):
179f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    future_models = [(name, self.GetModel(name)) for name in self.GetNames()]
180f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)    for name, future_model in future_models:
181f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      try:
182f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        model = future_model.Get()
183f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      except FileNotFoundError:
184f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        continue
185f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)      if model:
186f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)        yield name, model
187