1# Copyright 2014 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
5import HTMLParser
6import json
7import os
8import string
9
10from telemetry.web_components import web_components_project
11from tvcm import generate
12
13
14class WebComponent(object):
15  """An HTML-based viewer of data, built out of telemetry components.
16
17  A WebComponent is used to visualize complex data that is produced from a
18  telemtry benchmark. A WebComponent is a Javascript class that can be
19  data-bound, plus python-side bindings that let us write HTML files that
20  instantiate that class and bind it to specific data.
21
22  The primary job of the python side of a WebComponent is to implement the
23  WriteDataToFileAsJson. The data written here is passed to the
24  data_binding_property of the JS-side class.
25
26  The primary job of the javascript side of a WebComponent is visualization: it
27  takes the data from python and renders a UI for displaying that data in some
28  manner.
29
30  """
31  def __init__(self, tvcm_module_name, js_class_name, data_binding_property):
32    self._tvcm_module_name = tvcm_module_name
33    self._js_class_name = js_class_name
34    self._data_binding_property = data_binding_property
35    self._data_to_view = None
36
37  @property
38  def data_to_view(self):
39    return self._data_to_view
40
41  @data_to_view.setter
42  def data_to_view(self, data_to_view):
43    self._data_to_view = data_to_view
44
45  def WriteDataToFileAsJson(self, f):
46    raise NotImplementedError()
47
48  def GetDependentModuleNames(self):
49    return [self._tvcm_module_name]
50
51  def WriteWebComponentToFile(self, f):
52    project = web_components_project.WebComponentsProject()
53    load_sequence = project.CalcLoadSequenceForModuleNames(
54      self.GetDependentModuleNames())
55
56    with open(os.path.join(os.path.dirname(__file__),
57                           'web_component_bootstrap.js')) as bfile:
58      bootstrap_js_template = string.Template(bfile.read())
59    bootstrap_js = bootstrap_js_template.substitute(
60      js_class_name=self._js_class_name,
61      data_binding_property=self._data_binding_property)
62
63    bootstrap_script = generate.ExtraScript(text_content=bootstrap_js)
64
65    class WebComponentDataScript(generate.ExtraScript):
66      def __init__(self, results_component):
67        super(WebComponentDataScript, self).__init__()
68        self._results_component = results_component
69
70      def WriteToFile(self, output_file):
71        output_file.write('<script id="telemetry-web-component-data" ' +
72                          'type="application/json">\n')
73        self._results_component.WriteDataToFileAsJson(output_file)
74        output_file.write('</script>\n')
75
76
77    generate.GenerateStandaloneHTMLToFile(
78        f, load_sequence,
79        title='Telemetry results',
80        extra_scripts=[bootstrap_script, WebComponentDataScript(self)])
81
82  @staticmethod
83  def ReadDataObjectFromWebComponentFile(f):
84    """Reads the data inside a file written with WriteWebComponentToFile
85
86    Returns None if the data wasn't found, the JSON.parse'd object on success.
87    Raises exception if the HTML file was corrupt.
88
89    """
90    class MyHTMLParser(HTMLParser.HTMLParser):
91      def __init__(self):
92        HTMLParser.HTMLParser.__init__(self)
93        self._got_data_tag = False
94        self._in_data_tag = False
95        self._data_records = []
96
97      def handle_starttag(self, tag, attrs):
98        if tag != 'script':
99          return
100        id_attr = dict(attrs).get('id', None)
101        if id_attr == 'telemetry-web-component-data':
102          assert not self._got_data_tag
103          self._got_data_tag = True
104          self._in_data_tag = True
105
106      def handle_endtag(self, tag):
107        self._in_data_tag = False
108
109      def handle_data(self, data):
110        if self._in_data_tag:
111          self._data_records.append(data)
112
113      @property
114      def data(self):
115        if not self._got_data_tag:
116          raise Exception('Missing <script> with #telemetry-web-component-data')
117        if self._in_data_tag:
118          raise Exception('Missing </script> on #telemetry-web-component-data')
119        return json.loads(''.join(self._data_records))
120
121    parser = MyHTMLParser()
122    for line in f:
123      parser.feed(line)
124    return parser.data
125