| OLD | NEW |
| 1 # Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import posixpath | 5 import posixpath |
| 6 | 6 |
| 7 from compiled_file_system import SingleFile, Unicode | 7 from compiled_file_system import SingleFile, Unicode |
| 8 from extensions_paths import API_PATHS | 8 from extensions_paths import API_PATHS |
| 9 from features_bundle import HasParent, GetParentName | 9 from features_bundle import HasParent, GetParentName |
| 10 from file_system import FileNotFoundError | 10 from file_system import FileNotFoundError |
| 11 from future import All, Future | 11 from future import All, Future, Race |
| 12 from operator import itemgetter | 12 from operator import itemgetter |
| 13 from path_util import Join |
| 13 from platform_util import PlatformToExtensionType | 14 from platform_util import PlatformToExtensionType |
| 14 from schema_util import ProcessSchema | 15 from schema_util import ProcessSchema |
| 15 from third_party.json_schema_compiler.json_schema import DeleteNodes | 16 from third_party.json_schema_compiler.json_schema import DeleteNodes |
| 16 from third_party.json_schema_compiler.model import Namespace, UnixName | 17 from third_party.json_schema_compiler.model import Namespace, UnixName |
| 17 | 18 |
| 18 | 19 |
| 19 def GetNodeCategories(): | 20 def GetNodeCategories(): |
| 20 '''Returns a tuple of the possible categories a node may belong to. | 21 '''Returns a tuple of the possible categories a node may belong to. |
| 21 ''' | 22 ''' |
| 22 return ('types', 'functions', 'events', 'properties') | 23 return ('types', 'functions', 'events', 'properties') |
| (...skipping 28 matching lines...) Expand all Loading... |
| 51 '''Tracks APIs and their Models. | 52 '''Tracks APIs and their Models. |
| 52 ''' | 53 ''' |
| 53 | 54 |
| 54 def __init__(self, | 55 def __init__(self, |
| 55 features_bundle, | 56 features_bundle, |
| 56 compiled_fs_factory, | 57 compiled_fs_factory, |
| 57 file_system, | 58 file_system, |
| 58 object_store_creator, | 59 object_store_creator, |
| 59 platform): | 60 platform): |
| 60 self._features_bundle = features_bundle | 61 self._features_bundle = features_bundle |
| 62 self._file_system = file_system |
| 61 self._platform = PlatformToExtensionType(platform) | 63 self._platform = PlatformToExtensionType(platform) |
| 62 self._model_cache = compiled_fs_factory.Create( | 64 self._model_cache = compiled_fs_factory.Create( |
| 63 file_system, self._CreateAPIModel, APIModels, category=self._platform) | 65 file_system, self._CreateAPIModel, APIModels, category=self._platform) |
| 64 self._object_store = object_store_creator.Create(APIModels) | 66 self._object_store = object_store_creator.Create(APIModels) |
| 65 | 67 |
| 66 @SingleFile | 68 @SingleFile |
| 67 @Unicode | 69 @Unicode |
| 68 def _CreateAPIModel(self, path, data): | 70 def _CreateAPIModel(self, path, data): |
| 69 def does_not_include_platform(node): | 71 def does_not_include_platform(node): |
| 70 return ('extension_types' in node and | 72 return ('extension_types' in node and |
| 71 node['extension_types'] != 'all' and | 73 node['extension_types'] != 'all' and |
| 72 self._platform not in node['extension_types']) | 74 self._platform not in node['extension_types']) |
| 73 | 75 |
| 74 schema = ProcessSchema(path, data)[0] | 76 schema = ProcessSchema(path, data)[0] |
| 75 if not schema: | 77 if not schema: |
| 76 raise ValueError('No schema for %s' % path) | 78 raise ValueError('No schema for %s' % path) |
| 77 return Namespace(DeleteNodes( | 79 return Namespace(DeleteNodes( |
| 78 schema, matcher=does_not_include_platform), schema['namespace']) | 80 schema, matcher=does_not_include_platform), schema['namespace']) |
| 79 | 81 |
| 80 def GetNames(self): | 82 def GetNames(self): |
| 81 # API names appear alongside some of their methods/events/etc in the | 83 # API names appear alongside some of their methods/events/etc in the |
| 82 # features file. APIs are those which either implicitly or explicitly have | 84 # features file. APIs are those which either implicitly or explicitly have |
| 83 # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are | 85 # no parent feature (e.g. app, app.window, and devtools.inspectedWindow are |
| 84 # APIs; runtime.onConnectNative is not). | 86 # APIs; runtime.onConnectNative is not). |
| 85 api_features = self._features_bundle.GetAPIFeatures().Get() | 87 api_features = self._features_bundle.GetAPIFeatures().Get() |
| 86 return [name for name, feature in api_features.iteritems() | 88 return [name for name, feature in api_features.iteritems() |
| 87 if not HasParent(name, feature, api_features)] | 89 if not HasParent(name, feature, api_features)] |
| 88 | 90 |
| 89 def GetModel(self, api_name): | 91 def _GetPotentialPathsForModel(self, api_name): |
| 92 '''Returns the list of file system paths that the model for |api_name| |
| 93 might be located at. |
| 94 ''' |
| 90 # By default |api_name| is assumed to be given without a path or extension, | 95 # By default |api_name| is assumed to be given without a path or extension, |
| 91 # so combinations of known paths and extension types will be searched. | 96 # so combinations of known paths and extension types will be searched. |
| 92 api_extensions = ('.json', '.idl') | 97 api_extensions = ('.json', '.idl') |
| 93 api_paths = API_PATHS | 98 api_paths = API_PATHS |
| 94 | 99 |
| 95 # Callers sometimes include a file extension and/or prefix path with the | 100 # Callers sometimes include a file extension and/or prefix path with the |
| 96 # |api_name| argument. We believe them and narrow the search space | 101 # |api_name| argument. We believe them and narrow the search space |
| 97 # accordingly. | 102 # accordingly. |
| 98 name, ext = posixpath.splitext(api_name) | 103 name, ext = posixpath.splitext(api_name) |
| 99 if ext in api_extensions: | 104 if ext in api_extensions: |
| 100 api_extensions = (ext,) | 105 api_extensions = (ext,) |
| 101 api_name = name | 106 api_name = name |
| 102 for api_path in api_paths: | 107 for api_path in api_paths: |
| 103 if api_name.startswith(api_path): | 108 if api_name.startswith(api_path): |
| 104 api_name = api_name[len(api_path):] | 109 api_name = api_name[len(api_path):] |
| 105 api_paths = (api_path,) | 110 api_paths = (api_path,) |
| 106 break | 111 break |
| 107 | 112 |
| 108 # API names are given as declarativeContent and app.window but file names | 113 # API names are given as declarativeContent and app.window but file names |
| 109 # will be declarative_content and app_window. | 114 # will be declarative_content and app_window. |
| 110 file_name = UnixName(api_name).replace('.', '_') | 115 file_name = UnixName(api_name).replace('.', '_') |
| 111 # Devtools APIs are in API/devtools/ not API/, and have their | 116 # Devtools APIs are in API/devtools/ not API/, and have their |
| 112 # "devtools" names removed from the file names. | 117 # "devtools" names removed from the file names. |
| 113 basename = posixpath.basename(file_name) | 118 basename = posixpath.basename(file_name) |
| 114 if 'devtools_' in basename: | 119 if 'devtools_' in basename: |
| 115 file_name = posixpath.join( | 120 file_name = posixpath.join( |
| 116 'devtools', file_name.replace(basename, | 121 'devtools', file_name.replace(basename, |
| 117 basename.replace('devtools_' , ''))) | 122 basename.replace('devtools_' , ''))) |
| 118 | 123 |
| 119 futures = [self._model_cache.GetFromFile( | 124 return [Join(path, file_name + ext) for ext in api_extensions |
| 120 posixpath.join(path, '%s%s' % (file_name, ext))) | 125 for path in api_paths] |
| 121 for ext in api_extensions | 126 |
| 122 for path in api_paths] | 127 def GetModel(self, api_name): |
| 123 def resolve(): | 128 futures = [self._model_cache.GetFromFile(path) |
| 124 for future in futures: | 129 for path in self._GetPotentialPathsForModel(api_name)] |
| 125 try: | 130 return Race(futures, except_pass=(FileNotFoundError, ValueError)) |
| 126 return future.Get() | |
| 127 # Either the file wasn't found or there was no schema for the file | |
| 128 except (FileNotFoundError, ValueError): pass | |
| 129 # Propagate the first error if neither were found. | |
| 130 futures[0].Get() | |
| 131 return Future(callback=resolve) | |
| 132 | 131 |
| 133 def GetContentScriptAPIs(self): | 132 def GetContentScriptAPIs(self): |
| 134 '''Creates a dict of APIs and nodes supported by content scripts in | 133 '''Creates a dict of APIs and nodes supported by content scripts in |
| 135 this format: | 134 this format: |
| 136 | 135 |
| 137 { | 136 { |
| 138 'extension': '<ContentScriptAPI name='extension', | 137 'extension': '<ContentScriptAPI name='extension', |
| 139 restrictedTo=[{'node': 'onRequest'}]>', | 138 restrictedTo=[{'node': 'onRequest'}]>', |
| 140 ... | 139 ... |
| 141 } | 140 } |
| (...skipping 20 matching lines...) Expand all Loading... |
| 162 content_script_apis[parent] = ContentScriptAPI(parent) | 161 content_script_apis[parent] = ContentScriptAPI(parent) |
| 163 if content_script_apis[parent].restrictedTo: | 162 if content_script_apis[parent].restrictedTo: |
| 164 content_script_apis[parent].restrictedTo.append(node) | 163 content_script_apis[parent].restrictedTo.append(node) |
| 165 else: | 164 else: |
| 166 content_script_apis[parent].restrictedTo = [node] | 165 content_script_apis[parent].restrictedTo = [node] |
| 167 | 166 |
| 168 self._object_store.Set('content_script_apis', content_script_apis) | 167 self._object_store.Set('content_script_apis', content_script_apis) |
| 169 return content_script_apis | 168 return content_script_apis |
| 170 return Future(callback=resolve) | 169 return Future(callback=resolve) |
| 171 | 170 |
| 172 def Cron(self): | |
| 173 futures = [self.GetModel(name) for name in self.GetNames()] | |
| 174 return All(futures, except_pass=(FileNotFoundError, ValueError)) | |
| 175 | |
| 176 def IterModels(self): | 171 def IterModels(self): |
| 177 future_models = [(name, self.GetModel(name)) for name in self.GetNames()] | 172 future_models = [(name, self.GetModel(name)) for name in self.GetNames()] |
| 178 for name, future_model in future_models: | 173 for name, future_model in future_models: |
| 179 try: | 174 try: |
| 180 model = future_model.Get() | 175 model = future_model.Get() |
| 181 except FileNotFoundError: | 176 except FileNotFoundError: |
| 182 continue | 177 continue |
| 183 if model: | 178 if model: |
| 184 yield name, model | 179 yield name, model |
| 180 |
| 181 def Cron(self): |
| 182 def map_cron_paths(op): |
| 183 results = [] |
| 184 for name in self.GetNames(): |
| 185 results += [op(path) for path in self._GetPotentialPathsForModel(name)] |
| 186 return results |
| 187 |
| 188 # Immediately stat everything so that files are guaranteed to be eventually |
| 189 # up to date. See http://crbug.com/398042 for background. |
| 190 # XXX see comment below, return a future for this, hopefully crons will |
| 191 # complete and we don't need to immediately resolve? |
| 192 All(map_cron_paths(self._file_system.StatAsync), |
| 193 except_pass=(FileNotFoundError, ValueError)).Get() |
| 194 |
| 195 # Update content in the future. |
| 196 # XXX don't do this for now, see how the crons and instances behave with |
| 197 # just getting the versions. |
| 198 return All(map_cron_paths(self.GetModel), |
| 199 except_pass=(FileNotFoundError, ValueError)) |
| OLD | NEW |