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