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"""This module contains the Module class and other classes for resources. 6 7The Module class represents a module in the trace viewer system. A module has 8a name, and may require a variety of other resources, such as stylesheets, 9template objects, raw JavaScript, or other modules. 10 11Other resources include HTML templates, raw JavaScript files, and stylesheets. 12""" 13 14import os 15import inspect 16import codecs 17 18from tvcm import js_utils 19 20 21class DepsException(Exception): 22 """Exceptions related to module dependency resolution.""" 23 24 def __init__(self, fmt, *args): 25 from tvcm import style_sheet as style_sheet_module 26 context = [] 27 frame = inspect.currentframe() 28 while frame: 29 frame_locals = frame.f_locals 30 31 module_name = None 32 if 'self' in frame_locals: 33 s = frame_locals['self'] 34 if isinstance(s, Module): 35 module_name = s.name 36 if isinstance(s, style_sheet_module.StyleSheet): 37 module_name = s.name + '.css' 38 if not module_name: 39 if 'module' in frame_locals: 40 module = frame_locals['module'] 41 if isinstance(s, Module): 42 module_name = module.name 43 elif 'm' in frame_locals: 44 module = frame_locals['m'] 45 if isinstance(s, Module): 46 module_name = module.name 47 48 if module_name: 49 if len(context): 50 if context[-1] != module_name: 51 context.append(module_name) 52 else: 53 context.append(module_name) 54 55 frame = frame.f_back 56 57 context.reverse() 58 self.context = context 59 context_str = '\n'.join(' %s' % x for x in context) 60 Exception.__init__( 61 self, 'While loading:\n%s\nGot: %s' % (context_str, (fmt % args))) 62 63 64class ModuleDependencyMetadata(object): 65 66 def __init__(self): 67 self.dependent_module_names = [] 68 self.dependent_raw_script_relative_paths = [] 69 self.style_sheet_names = [] 70 71 def AppendMetdata(self, other): 72 self.dependent_module_names += other.dependent_module_names 73 self.dependent_raw_script_relative_paths += \ 74 other.dependent_raw_script_relative_paths 75 self.style_sheet_names += other.style_sheet_names 76 77 78_next_module_id = 1 79 80 81class Module(object): 82 """Represents a JavaScript module. 83 84 Interesting properties include: 85 name: Module name, may include a namespace, e.g. 'tvcm.foo'. 86 filename: The filename of the actual module. 87 contents: The text contents of the module. 88 dependent_modules: Other modules that this module depends on. 89 90 In addition to these properties, a Module also contains lists of other 91 resources that it depends on. 92 """ 93 94 def __init__(self, loader, name, resource, load_resource=True): 95 assert isinstance(name, basestring), 'Got %s instead' % repr(name) 96 97 global _next_module_id 98 self._id = _next_module_id 99 _next_module_id += 1 100 101 self.loader = loader 102 self.name = name 103 self.resource = resource 104 105 if load_resource: 106 f = codecs.open(self.filename, mode='r', encoding='utf-8') 107 self.contents = f.read() 108 f.close() 109 else: 110 self.contents = None 111 112 # Dependency metadata, set up during Parse(). 113 self.dependency_metadata = None 114 115 # Actual dependencies, set up during load(). 116 self.dependent_modules = [] 117 self.dependent_raw_scripts = [] 118 self.style_sheets = [] 119 120 # Caches. 121 self._all_dependent_modules_recursive = None 122 123 def __repr__(self): 124 return '%s(%s)' % (self.__class__.__name__, self.name) 125 126 @property 127 def id(self): 128 return self._id 129 130 @property 131 def filename(self): 132 return self.resource.absolute_path 133 134 def isComponent(self): 135 ref = os.path.join('third_party', 'components') 136 return ref in self.filename 137 138 def Parse(self): 139 """Parses self.contents and fills in the module's dependency metadata.""" 140 raise NotImplementedError() 141 142 def GetTVCMDepsModuleType(self): 143 """Returns the tvcm.setModuleInfo type for this module""" 144 raise NotImplementedError() 145 146 def AppendJSContentsToFile(self, 147 f, 148 use_include_tags_for_scripts, 149 dir_for_include_tag_root): 150 """Appends the js for this module to the provided file.""" 151 for dependent_raw_script in self.dependent_raw_scripts: 152 if use_include_tags_for_scripts: 153 rel_filename = os.path.relpath(dependent_raw_script.filename, 154 dir_for_include_tag_root) 155 f.write("""<include src="%s">\n""" % rel_filename) 156 else: 157 f.write(js_utils.EscapeJSIfNeeded(dependent_raw_script.contents)) 158 f.write('\n') 159 160 def AppendHTMLContentsToFile(self, f, ctl, minify=False): 161 """Appends the HTML for this module [without links] to the provided file.""" 162 pass 163 164 def Load(self): 165 """Loads the sub-resources that this module depends on from its dependency 166 metadata. 167 168 Raises: 169 DepsException: There was a problem finding one of the dependencies. 170 Exception: There was a problem parsing a module that this one depends on. 171 """ 172 assert self.name, 'Module name must be set before dep resolution.' 173 assert self.filename, 'Module filename must be set before dep resolution.' 174 assert self.name in self.loader.loaded_modules, ( 175 'Module must be registered in resource loader before loading.') 176 177 metadata = self.dependency_metadata 178 for name in metadata.dependent_module_names: 179 module = self.loader.LoadModule(module_name=name) 180 self.dependent_modules.append(module) 181 182 for path in metadata.dependent_raw_script_relative_paths: 183 raw_script = self.loader.LoadRawScript(path) 184 self.dependent_raw_scripts.append(raw_script) 185 186 for name in metadata.style_sheet_names: 187 style_sheet = self.loader.LoadStyleSheet(name) 188 self.style_sheets.append(style_sheet) 189 190 @property 191 def all_dependent_modules_recursive(self): 192 if self._all_dependent_modules_recursive: 193 return self._all_dependent_modules_recursive 194 195 self._all_dependent_modules_recursive = set(self.dependent_modules) 196 for dependent_module in self.dependent_modules: 197 self._all_dependent_modules_recursive.update( 198 dependent_module.all_dependent_modules_recursive) 199 return self._all_dependent_modules_recursive 200 201 def ComputeLoadSequenceRecursive(self, load_sequence, already_loaded_set, 202 depth=0): 203 """Recursively builds up a load sequence list. 204 205 Args: 206 load_sequence: A list which will be incrementally built up. 207 already_loaded_set: A set of modules that has already been added to the 208 load sequence list. 209 depth: The depth of recursion. If it too deep, that indicates a loop. 210 """ 211 if depth > 32: 212 raise Exception('Include loop detected on %s', self.name) 213 for dependent_module in self.dependent_modules: 214 if dependent_module.name in already_loaded_set: 215 continue 216 dependent_module.ComputeLoadSequenceRecursive( 217 load_sequence, already_loaded_set, depth + 1) 218 if self.name not in already_loaded_set: 219 already_loaded_set.add(self.name) 220 load_sequence.append(self) 221 222 def GetAllDependentFilenamesRecursive(self, include_raw_scripts=True): 223 dependent_filenames = [] 224 225 visited_modules = set() 226 227 def Get(module): 228 module.AppendDirectlyDependentFilenamesTo( 229 dependent_filenames, include_raw_scripts) 230 visited_modules.add(module) 231 for m in module.dependent_modules: 232 if m in visited_modules: 233 continue 234 Get(m) 235 236 Get(self) 237 return dependent_filenames 238 239 def AppendDirectlyDependentFilenamesTo( 240 self, dependent_filenames, include_raw_scripts=True): 241 dependent_filenames.append(self.resource.absolute_path) 242 if include_raw_scripts: 243 for raw_script in self.dependent_raw_scripts: 244 dependent_filenames.append(raw_script.resource.absolute_path) 245 for style_sheet in self.style_sheets: 246 style_sheet.AppendDirectlyDependentFilenamesTo(dependent_filenames) 247 248 249class RawScript(object): 250 """Represents a raw script resource referenced by a module via the 251 tvcm.requireRawScript(xxx) directive.""" 252 253 def __init__(self, resource): 254 self.resource = resource 255 256 @property 257 def filename(self): 258 return self.resource.absolute_path 259 260 @property 261 def contents(self): 262 return self.resource.contents 263 264 def __repr__(self): 265 return 'RawScript(%s)' % self.filename 266