1# Copyright (c) 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
5from __future__ import absolute_import
6from __future__ import division
7from __future__ import print_function
8
9import os
10import subprocess
11import sys
12import tempfile
13
14from py_vulcanize import html_generation_controller
15
16try:
17  from six import StringIO
18except ImportError:
19  from io import StringIO
20
21
22
23html_warning_message = """
24
25
26<!--
27WARNING: This file is auto generated.
28
29         Do not edit directly.
30-->
31"""
32
33js_warning_message = """
34// Copyright 2015 The Chromium Authors. All rights reserved.
35// Use of this source code is governed by a BSD-style license that can be
36// found in the LICENSE file.
37
38/* WARNING: This file is auto generated.
39 *
40 * Do not edit directly.
41 */
42"""
43
44css_warning_message = """
45/* Copyright 2015 The Chromium Authors. All rights reserved.
46 * Use of this source code is governed by a BSD-style license that can be
47 * found in the LICENSE file. */
48
49/* WARNING: This file is auto-generated.
50 *
51 * Do not edit directly.
52 */
53"""
54
55origin_trial_tokens = [
56  # WebComponent V0 origin trial token for googleusercontent.com + subdomains.
57  # This is the domain from which traces in cloud storage are served.
58  # Expires Nov 5, 2020. See https://crbug.com/1021137
59  "AnYuQDtUf6OrWCmR9Okd67JhWVTbmnRedvPi1TEvAxac8+1p6o9q08FoDO6oCbLD0xEqev+SkZFiIhFSzlY9HgUAAABxeyJvcmlnaW4iOiJodHRwczovL2dvb2dsZXVzZXJjb250ZW50LmNvbTo0NDMiLCJmZWF0dXJlIjoiV2ViQ29tcG9uZW50c1YwIiwiZXhwaXJ5IjoxNjA0NjE0NTM4LCJpc1N1YmRvbWFpbiI6dHJ1ZX0=",
60  # This is for chromium-build-stats.appspot.com (ukai@)
61  # Expires Feb 2, 2021. see https://crbug.com/1050215
62  "AkFXw3wHnOs/XXYqFXpc3diDLrRFd9PTgGs/gs43haZmngI/u1g8L4bDnSKLZkB6fecjmjTwcAMQFCpWMAoHSQEAAAB8eyJvcmlnaW4iOiJodHRwczovL2Nocm9taXVtLWJ1aWxkLXN0YXRzLmFwcHNwb3QuY29tOjQ0MyIsImZlYXR1cmUiOiJXZWJDb21wb25lbnRzVjAiLCJleHBpcnkiOjE2MTIyMjM5OTksImlzU3ViZG9tYWluIjp0cnVlfQ==",
63  # This is for chromium-build-stats-staging.appspot.com (ukai@)
64  # Expires Feb 2, 2021, see https://crbug.com/1050215
65  "AtQY4wpX9+nj+Vn27cTgygzIPbtB2WoAoMQR5jK9mCm/H2gRIDH6MmGVAaziv9XnYTDKjhBnQYtecbTiIHCQiAIAAACEeyJvcmlnaW4iOiJodHRwczovL2Nocm9taXVtLWJ1aWxkLXN0YXRzLXN0YWdpbmcuYXBwc3BvdC5jb206NDQzIiwiZmVhdHVyZSI6IldlYkNvbXBvbmVudHNWMCIsImV4cGlyeSI6MTYxMjIyMzk5OSwiaXNTdWJkb21haW4iOnRydWV9"
66  #
67  # Add more tokens here if traces are served from other domains.
68  # WebComponent V0 origin tiral token is generated on
69  # https://developers.chrome.com/origintrials/#/trials/active
70]
71
72def _AssertIsUTF8(f):
73  if isinstance(f, StringIO):
74    return
75  assert f.encoding == 'utf-8'
76
77
78def _MinifyJS(input_js):
79  py_vulcanize_path = os.path.abspath(os.path.join(
80      os.path.dirname(__file__), '..'))
81  rjsmin_path = os.path.abspath(
82      os.path.join(py_vulcanize_path, 'third_party', 'rjsmin', 'rjsmin.py'))
83
84  with tempfile.NamedTemporaryFile() as _:
85    args = [
86        'python',
87        rjsmin_path
88    ]
89    p = subprocess.Popen(args,
90                         stdin=subprocess.PIPE,
91                         stdout=subprocess.PIPE,
92                         stderr=subprocess.PIPE)
93    res = p.communicate(input=input_js.encode('utf-8'))
94    errorcode = p.wait()
95    if errorcode != 0:
96      sys.stderr.write('rJSmin exited with error code %d' % errorcode)
97      sys.stderr.write(res[1])
98      raise Exception('Failed to minify, omgah')
99    return res[0].decode('utf-8')
100
101
102def GenerateJS(load_sequence,
103               use_include_tags_for_scripts=False,
104               dir_for_include_tag_root=None,
105               minify=False,
106               report_sizes=False):
107  f = StringIO()
108  GenerateJSToFile(f,
109                   load_sequence,
110                   use_include_tags_for_scripts,
111                   dir_for_include_tag_root,
112                   minify=minify,
113                   report_sizes=report_sizes)
114
115  return f.getvalue()
116
117
118def GenerateJSToFile(f,
119                     load_sequence,
120                     use_include_tags_for_scripts=False,
121                     dir_for_include_tag_root=None,
122                     minify=False,
123                     report_sizes=False):
124  _AssertIsUTF8(f)
125  if use_include_tags_for_scripts and dir_for_include_tag_root is None:
126    raise Exception('Must provide dir_for_include_tag_root')
127
128  f.write(js_warning_message)
129  f.write('\n')
130
131  if not minify:
132    flatten_to_file = f
133  else:
134    flatten_to_file = StringIO()
135
136  for module in load_sequence:
137    module.AppendJSContentsToFile(flatten_to_file,
138                                  use_include_tags_for_scripts,
139                                  dir_for_include_tag_root)
140  if minify:
141    js = flatten_to_file.getvalue()
142    minified_js = _MinifyJS(js)
143    f.write(minified_js)
144    f.write('\n')
145
146  if report_sizes:
147    for module in load_sequence:
148      s = StringIO()
149      module.AppendJSContentsToFile(s,
150                                    use_include_tags_for_scripts,
151                                    dir_for_include_tag_root)
152
153      # Add minified size info.
154      js = s.getvalue()
155      min_js_size = str(len(_MinifyJS(js)))
156
157      # Print names for this module. Some domain-specific simplifications
158      # are included to make pivoting more obvious.
159      parts = module.name.split('.')
160      if parts[:2] == ['base', 'ui']:
161        parts = ['base_ui'] + parts[2:]
162      if parts[:2] == ['tracing', 'importer']:
163        parts = ['importer'] + parts[2:]
164      tln = parts[0]
165      sln = '.'.join(parts[:2])
166
167      # Output
168      print(('%i\t%s\t%s\t%s\t%s' %
169             (len(js), min_js_size, module.name, tln, sln)))
170      sys.stdout.flush()
171
172
173class ExtraScript(object):
174
175  def __init__(self, script_id=None, text_content=None, content_type=None):
176    if script_id is not None:
177      assert script_id[0] != '#'
178    self.script_id = script_id
179    self.text_content = text_content
180    self.content_type = content_type
181
182  def WriteToFile(self, output_file):
183    _AssertIsUTF8(output_file)
184    attrs = []
185    if self.script_id:
186      attrs.append('id="%s"' % self.script_id)
187    if self.content_type:
188      attrs.append('content-type="%s"' % self.content_type)
189
190    if len(attrs) > 0:
191      output_file.write('<script %s>\n' % ' '.join(attrs))
192    else:
193      output_file.write('<script>\n')
194    if self.text_content:
195      output_file.write(self.text_content)
196    output_file.write('</script>\n')
197
198
199def _MinifyCSS(css_text):
200  py_vulcanize_path = os.path.abspath(os.path.join(
201      os.path.dirname(__file__), '..'))
202  rcssmin_path = os.path.abspath(
203      os.path.join(py_vulcanize_path, 'third_party', 'rcssmin', 'rcssmin.py'))
204
205  with tempfile.NamedTemporaryFile() as _:
206    rcssmin_args = ['python', rcssmin_path]
207    p = subprocess.Popen(rcssmin_args,
208                         stdin=subprocess.PIPE,
209                         stdout=subprocess.PIPE,
210                         stderr=subprocess.PIPE)
211    res = p.communicate(input=css_text)
212    errorcode = p.wait()
213    if errorcode != 0:
214      sys.stderr.write('rCSSmin exited with error code %d' % errorcode)
215      sys.stderr.write(res[1])
216      raise Exception('Failed to generate css for %s.' % css_text)
217    return res[0]
218
219
220def GenerateStandaloneHTMLAsString(*args, **kwargs):
221  f = StringIO()
222  GenerateStandaloneHTMLToFile(f, *args, **kwargs)
223  return f.getvalue()
224
225def _WriteOriginTrialTokens(output_file):
226  for token in origin_trial_tokens:
227    output_file.write('  <meta http-equiv="origin-trial" content="')
228    output_file.write(token)
229    output_file.write('">\n')
230
231def GenerateStandaloneHTMLToFile(output_file,
232                                 load_sequence,
233                                 title=None,
234                                 flattened_js_url=None,
235                                 extra_scripts=None,
236                                 minify=False,
237                                 report_sizes=False,
238                                 output_html_head_and_body=True):
239  """Writes a HTML file with the content of all modules in a load sequence.
240
241  The load_sequence is a list of (HTML or JS) Module objects; the order that
242  they're inserted into the file depends on their type and position in the load
243  sequence.
244  """
245  _AssertIsUTF8(output_file)
246  extra_scripts = extra_scripts or []
247
248  if output_html_head_and_body:
249    output_file.write(
250        '<!DOCTYPE html>\n'
251        '<html>\n'
252        '  <head i18n-values="dir:textdirection;">\n'
253        '  <meta http-equiv="Content-Type" content="text/html;'
254        'charset=utf-8">\n')
255    _WriteOriginTrialTokens(output_file)
256    if title:
257      output_file.write('  <title>%s</title>\n  ' % title)
258  else:
259    assert title is None
260
261  loader = load_sequence[0].loader
262
263  written_style_sheets = set()
264
265  class HTMLGenerationController(
266      html_generation_controller.HTMLGenerationController):
267
268    def __init__(self, module):
269      self.module = module
270
271    def GetHTMLForStylesheetHRef(self, href):
272      resource = self.module.HRefToResource(
273          href, '<link rel="stylesheet" href="%s">' % href)
274      style_sheet = loader.LoadStyleSheet(resource.name)
275
276      if style_sheet in written_style_sheets:
277        return None
278      written_style_sheets.add(style_sheet)
279
280      text = style_sheet.contents_with_inlined_images
281      if minify:
282        text = _MinifyCSS(text)
283      return '<style>\n%s\n</style>' % text
284
285  for module in load_sequence:
286    controller = HTMLGenerationController(module)
287    module.AppendHTMLContentsToFile(output_file, controller, minify=minify)
288
289  if flattened_js_url:
290    output_file.write('<script src="%s"></script>\n' % flattened_js_url)
291  else:
292    output_file.write('<script>\n')
293    js = GenerateJS(load_sequence, minify=minify, report_sizes=report_sizes)
294    output_file.write(js)
295    output_file.write('</script>\n')
296
297  for extra_script in extra_scripts:
298    extra_script.WriteToFile(output_file)
299
300  if output_html_head_and_body:
301    output_file.write('</head>\n  <body>\n  </body>\n</html>\n')
302