• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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