Update to latest trace viewer

Upstream trace viewer has changed substantially since last pull.

First, the old flattening into js + css + html workflow has been replaced with
a new flatten into single html file workflow.

Second, trace viewer has moved to git.

Some pieces that were previously only in systrace are now upstream as well.
In particular, minification is now upstream. Thus, the minifying features in
systrace can be removed.

Change-Id: Ibc6a46fa3dccff8b771a95aae1909cf178157264
diff --git a/trace-viewer/hooks/__init__.py b/trace-viewer/hooks/__init__.py
new file mode 100644
index 0000000..3ee068d
--- /dev/null
+++ b/trace-viewer/hooks/__init__.py
@@ -0,0 +1,5 @@
+# Copyright (c) 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import os
+import sys
diff --git a/trace-viewer/hooks/css_presubmit_checker.py b/trace-viewer/hooks/css_presubmit_checker.py
new file mode 100644
index 0000000..910da80
--- /dev/null
+++ b/trace-viewer/hooks/css_presubmit_checker.py
@@ -0,0 +1,226 @@
+# Copyright (c) 2012 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import re
+
+class CSSChecker(object):
+  def __init__(self, input_api, output_api, file_filter=None):
+    self.input_api = input_api
+    self.output_api = output_api
+    self.file_filter = file_filter
+
+  def RunChecks(self):
+    # We use this a lot, so make a nick name variable.
+    def _collapseable_hex(s):
+      return (len(s) == 6 and s[0] == s[1] and s[2] == s[3] and s[4] == s[5])
+
+    def _is_gray(s):
+      return s[0] == s[1] == s[2] if len(s) == 3 else s[0:2] == s[2:4] == s[4:6]
+
+    def _remove_all(s):
+      return _remove_grit(_remove_ats(_remove_comments(s)))
+
+    def _remove_ats(s):
+      return re.sub(re.compile(r'@\w+.*?{(.*{.*?})+.*?}', re.DOTALL), '\\1', s)
+
+    def _remove_comments(s):
+      return re.sub(re.compile(r'/\*.*?\*/', re.DOTALL), '', s)
+
+    def _remove_grit(s):
+      grit_reg = r'<if[^>]+>.*?<\s*/\s*if[^>]*>|<include[^>]+>'
+      return re.sub(re.compile(grit_reg, re.DOTALL), '', s)
+
+    def _rgb_from_hex(s):
+      if len(s) == 3:
+        r, g, b = s[0] + s[0], s[1] + s[1], s[2] + s[2]
+      else:
+        r, g, b = s[0:2], s[2:4], s[4:6]
+      return int(r, base=16), int(g, base=16), int(b, base=16)
+
+    def alphabetize_props(contents):
+      errors = []
+      for rule in re.finditer(r'{(.*?)}', contents, re.DOTALL):
+        semis = map(lambda t: t.strip(), rule.group(1).split(';'))[:-1]
+        rules = filter(lambda r: ': ' in r, semis)
+        props = map(lambda r: r[0:r.find(':')], rules)
+        if props != sorted(props):
+          errors.append('    %s;\nExpected: %s' % (';\n    '.join(rules), ','.join(list(sorted(props)))))
+      return errors
+
+    def braces_have_space_before_and_nothing_after(line):
+      return re.search(r'(?:^|\S){|{\s*\S+\s*$', line)
+
+    def classes_use_dashes(line):
+      # Intentionally dumbed down version of CSS 2.1 grammar for class without
+      # non-ASCII, escape chars, or whitespace.
+      m = re.search(r'\.(-?[_a-zA-Z0-9-]+).*[,{]\s*$', line)
+      return (m and (m.group(1).lower() != m.group(1) or
+                     m.group(1).find('_') >= 0))
+
+    # Ignore single frames in a @keyframe, i.e. 0% { margin: 50px; }
+    frame_reg = r'\s*\d+%\s*{\s*[_a-zA-Z0-9-]+:(\s*[_a-zA-Z0-9-]+)+\s*;\s*}\s*'
+    def close_brace_on_new_line(line):
+      return (line.find('}') >= 0 and re.search(r'[^ }]', line) and
+              not re.match(frame_reg, line))
+
+    def colons_have_space_after(line):
+      return re.search(r'(?<!data):(?!//)\S[^;]+;\s*', line)
+
+    def favor_single_quotes(line):
+      return line.find('"') >= 0
+
+    # Shared between hex_could_be_shorter and rgb_if_not_gray.
+    hex_reg = (r'#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})(?=[^_a-zA-Z0-9-]|$)'
+               r'(?!.*(?:{.*|,\s*)$)')
+    def hex_could_be_shorter(line):
+      m = re.search(hex_reg, line)
+      return (m and _is_gray(m.group(1)) and _collapseable_hex(m.group(1)))
+
+    small_seconds = r'(?:^|[^_a-zA-Z0-9-])(0?\.[0-9]+)s(?!-?[_a-zA-Z0-9-])'
+    def milliseconds_for_small_times(line):
+      return re.search(small_seconds, line)
+
+    def no_data_uris_in_source_files(line):
+      return re.search(r'\(\s*\'?\s*data:', line)
+
+    def one_rule_per_line(line):
+      return re.search(r'[_a-zA-Z0-9-](?<!data):(?!//)[^;]+;\s*[^ }]\s*', line)
+
+    any_reg = re.compile(r':(?:-webkit-)?any\(.*?\)', re.DOTALL)
+    multi_sels = re.compile(r'(?:}[\n\s]*)?([^,]+,(?=[^{}]+?{).*[,{])\s*$',
+                            re.MULTILINE)
+    def one_selector_per_line(contents):
+      errors = []
+      for b in re.finditer(multi_sels, re.sub(any_reg, '', contents)):
+        errors.append('    ' + b.group(1).strip().splitlines()[-1:][0])
+      return errors
+
+    def rgb_if_not_gray(line):
+      m = re.search(hex_reg, line)
+      return (m and not _is_gray(m.group(1)))
+
+    def suggest_ms_from_s(line):
+      ms = int(float(re.search(small_seconds, line).group(1)) * 1000)
+      return ' (replace with %dms)' % ms
+
+    def suggest_rgb_from_hex(line):
+      suggestions = ['rgb(%d, %d, %d)' % _rgb_from_hex(h.group(1))
+          for h in re.finditer(hex_reg, line)]
+      return ' (replace with %s)' % ', '.join(suggestions)
+
+    def suggest_short_hex(line):
+      h = re.search(hex_reg, line).group(1)
+      return ' (replace with #%s)' % (h[0] + h[2] + h[4])
+
+    hsl = r'hsl\([^\)]*(?:[, ]|(?<=\())(?:0?\.?)?0%'
+    zeros = (r'^.*(?:^|\D)'
+             r'(?:\.0|0(?:\.0?|px|em|%|in|cm|mm|pc|pt|ex|deg|g?rad|m?s|k?hz))'
+             r'(?:\D|$)(?=[^{}]+?}).*$')
+    def zero_length_values(contents):
+      errors = []
+      for z in re.finditer(re.compile(zeros, re.MULTILINE), contents):
+        first_line = z.group(0).strip().splitlines()[0]
+        if not re.search(hsl, first_line):
+          errors.append('    ' + first_line)
+      return errors
+
+    added_or_modified_files_checks = [
+        { 'desc': 'Alphabetize properties and list vendor specific (i.e. '
+                  '-webkit) above standard.',
+          'test': alphabetize_props,
+          'multiline': True,
+        },
+        { 'desc': 'Start braces ({) end a selector, have a space before them '
+                  'and no rules after.',
+          'test': braces_have_space_before_and_nothing_after,
+        },
+        { 'desc': 'Classes use .dash-form.',
+          'test': classes_use_dashes,
+        },
+        { 'desc': 'Always put a rule closing brace (}) on a new line.',
+          'test': close_brace_on_new_line,
+        },
+        { 'desc': 'Colons (:) should have a space after them.',
+          'test': colons_have_space_after,
+        },
+        { 'desc': 'Use single quotes (\') instead of double quotes (") in '
+                  'strings.',
+          'test': favor_single_quotes,
+        },
+        { 'desc': 'Use abbreviated hex (#rgb) when in form #rrggbb.',
+          'test': hex_could_be_shorter,
+          'after': suggest_short_hex,
+        },
+        { 'desc': 'Use milliseconds for time measurements under 1 second.',
+          'test': milliseconds_for_small_times,
+          'after': suggest_ms_from_s,
+        },
+        { 'desc': 'Don\'t use data URIs in source files. Use grit instead.',
+          'test': no_data_uris_in_source_files,
+        },
+        { 'desc': 'One rule per line (what not to do: color: red; margin: 0;).',
+          'test': one_rule_per_line,
+        },
+        { 'desc': 'One selector per line (what not to do: a, b {}).',
+          'test': one_selector_per_line,
+          'multiline': True,
+        },
+        { 'desc': 'Use rgb() over #hex when not a shade of gray (like #333).',
+          'test': rgb_if_not_gray,
+          'after': suggest_rgb_from_hex,
+        },
+        { 'desc': 'Make all zero length terms (i.e. 0px) 0 unless inside of '
+                  'hsl() or part of @keyframe.',
+          'test': zero_length_values,
+          'multiline': True,
+        },
+    ]
+
+    results = []
+    affected_files = self.input_api.AffectedFiles(include_deletes=False,
+                                                  file_filter=self.file_filter)
+    files = []
+    for f in affected_files:
+      # Remove all /*comments*/, @at-keywords, and grit <if|include> tags; we're
+      # not using a real parser. TODO(dbeam): Check alpha in <if> blocks.
+      file_contents = _remove_all('\n'.join(f.new_contents))
+      files.append((f.filename, file_contents))
+
+    # Only look at CSS files for now.
+    for f in filter(lambda f: f[0].endswith('.css'), files):
+      file_errors = []
+      for check in added_or_modified_files_checks:
+        # If the check is multiline, it receieves the whole file and gives us
+        # back a list of things wrong. If the check isn't multiline, we pass it
+        # each line and the check returns something truthy if there's an issue.
+        if ('multiline' in check and check['multiline']):
+          check_errors = check['test'](f[1])
+          if len(check_errors) > 0:
+            # There are currently no multiline checks with ['after'].
+            file_errors.append('- %s\n%s' %
+                (check['desc'], '\n'.join(check_errors).rstrip()))
+        else:
+          check_errors = []
+          lines = f[1].splitlines()
+          for lnum in range(0, len(lines)):
+            line = lines[lnum]
+            if check['test'](line):
+              error = '    ' + line.strip()
+              if 'after' in check:
+                error += check['after'](line)
+              check_errors.append(error)
+          if len(check_errors) > 0:
+            file_errors.append('- %s\n%s' %
+                (check['desc'], '\n'.join(check_errors)))
+      if file_errors:
+        results.append(self.output_api.PresubmitPromptWarning(
+            '%s:\n%s' % (f[0], '\n\n'.join(file_errors))))
+
+    if results:
+      # Add your name if you're here often mucking around in the code.
+      authors = ['dbeam@chromium.org']
+      results.append(self.output_api.PresubmitNotifyResult(
+          'Was the CSS checker useful? Send feedback or hate mail to %s.' %
+          ', '.join(authors)))
+
+    return results
diff --git a/trace-viewer/hooks/gjslint.py b/trace-viewer/hooks/gjslint.py
new file mode 100644
index 0000000..505db18
--- /dev/null
+++ b/trace-viewer/hooks/gjslint.py
@@ -0,0 +1,44 @@
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Copyright (c) 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import os
+import sys
+
+from trace_viewer import trace_viewer_project
+
+def Main(paths_to_lint):
+  project = trace_viewer_project.TraceViewerProject()
+  new_paths = [
+    os.path.abspath(os.path.join(
+      project.trace_viewer_third_party_path, 'python_gflags')),
+    os.path.abspath(os.path.join(
+      project.trace_viewer_third_party_path, 'closure_linter'))
+  ]
+  sys.path += new_paths
+  try:
+    _MainImpl(paths_to_lint)
+  finally:
+    for p in new_paths:
+      sys.path.remove(p)
+
+def _MainImpl(paths_to_lint):
+  from closure_linter import gjslint
+
+  if sys.argv[1:] == ['--help']:
+    sys.exit(gjslint.main())
+
+  if len(sys.argv) > 1:
+    sys.stderr.write('No arguments allowed')
+    sys.exit(1)
+
+  sys.argv.append('--strict')
+  sys.argv.append('--unix_mode')
+  sys.argv.append('--check_html')
+  for p in paths_to_lint:
+    sys.argv.extend(['-r', os.path.relpath(p)])
+
+  gjslint.main()
diff --git a/trace-viewer/hooks/install.py b/trace-viewer/hooks/install.py
new file mode 100644
index 0000000..079ada9
--- /dev/null
+++ b/trace-viewer/hooks/install.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+# Copyright (c) 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import sys
+import os
+
+_TOP_PATH = os.path.abspath(os.path.join(
+    os.path.dirname(__file__), '..'))
+
+class Link(object):
+  def __init__(self, dst_path, src_path):
+    self.dst_path = dst_path
+    self.src_path = src_path
+
+  def Update(self):
+    full_src_path = os.path.join(_TOP_PATH, self.src_path)
+    full_dst_path = os.path.join(_TOP_PATH, self.dst_path)
+
+    full_dst_path_dirname = os.path.dirname(full_dst_path)
+
+    src_path_rel = os.path.relpath(full_src_path, full_dst_path_dirname)
+
+    assert os.path.exists(full_src_path)
+    if not os.path.exists(full_dst_path_dirname):
+      sys.stdout.write('ERROR\n\n')
+      sys.stdout.write(' dst dir doesn\'t exist\n' % self.full_dst_path_dirname)
+      sys.stdout.write('\n\n')
+      sys.exit(255)
+
+    if os.path.exists(full_dst_path) or os.path.islink(full_dst_path):
+      if not os.path.islink(full_dst_path):
+        sys.stdout.write('ERROR\n\n')
+        sys.stdout.write('  Cannot install %s, dst already exists:\n  %s\n' % (
+          os.path.basename(self.src_path), full_dst_path))
+        sys.stdout.write('\n\n')
+        sys.exit(255)
+
+      existing_src_path_rel = os.readlink(full_dst_path)
+      if existing_src_path_rel == src_path_rel:
+        return
+      else:
+        sys.stdout.write('ERROR\n\n')
+        sys.stdout.write('  Cannot install %s, because %s is linked elsewhere.\n' % (
+          os.path.basename(self.src_path),
+          os.path.relpath(full_dst_path)))
+        sys.stdout.write('\n\n')
+        sys.exit(255)
+
+    os.symlink(src_path_rel, full_dst_path)
+
+def InstallHooks():
+  if 'win' in sys.platform:
+    return
+
+  links = []
+  links.append(Link(os.path.join('.git', 'hooks', 'pre-commit'),
+                    os.path.join('hooks/pre_commit')))
+
+  for l in links:
+    l.Update()
diff --git a/trace-viewer/hooks/js_checks.py b/trace-viewer/hooks/js_checks.py
new file mode 100644
index 0000000..31b1f52
--- /dev/null
+++ b/trace-viewer/hooks/js_checks.py
@@ -0,0 +1,176 @@
+# Copyright (c) 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import os
+import sys
+import re
+
+class JSChecker(object):
+  def __init__(self, input_api, file_filter=None):
+    self.input_api = input_api
+    if file_filter:
+      self.file_filter = file_filter
+    else:
+      self.file_filter = lambda x: True
+
+  def RegexCheck(self, line_number, line, regex, message):
+    """Searches for |regex| in |line| to check for a particular style
+       violation, returning a message like the one below if the regex matches.
+       The |regex| must have exactly one capturing group so that the relevant
+       part of |line| can be highlighted. If more groups are needed, use
+       "(?:...)" to make a non-capturing group. Sample message:
+
+       line 6: Use var instead of const.
+           const foo = bar();
+           ^^^^^
+    """
+    match = re.search(regex, line)
+    if match:
+      assert len(match.groups()) == 1
+      start = match.start(1)
+      length = match.end(1) - start
+      return '  line %d: %s\n%s\n%s' % (
+          line_number,
+          message,
+          line,
+          self.error_highlight(start, length))
+    return ''
+
+  def ConstCheck(self, i, line):
+    """Check for use of the 'const' keyword."""
+    if re.search(r'\*\s+@const', line):
+      # Probably a JsDoc line
+      return ''
+
+    return self.RegexCheck(i, line, r'(?:^|\s|\()(const)\s',
+        'Use var instead of const.')
+
+  def error_highlight(self, start, length):
+    """Takes a start position and a length, and produces a row of '^'s to
+       highlight the corresponding part of a string.
+    """
+    return start * ' ' + length * '^'
+
+  def _makeErrorOrWarning(self, error_text, filename):
+    return error_text
+
+  def RunChecks(self):
+    """Check for violations of the Chromium JavaScript style guide. See
+       http://chromium.org/developers/web-development-style-guide#TOC-JavaScript
+    """
+
+    import sys
+    import warnings
+    old_path = sys.path
+    old_filters = warnings.filters
+
+    try:
+      base_path = os.path.abspath(os.path.join(
+          os.path.dirname(__file__), '..'))
+      closure_linter_path = os.path.join(
+          base_path, 'third_party', 'closure_linter')
+      gflags_path = os.path.join(
+          base_path, 'third_party', 'python_gflags')
+      sys.path.insert(0, closure_linter_path)
+      sys.path.insert(0, gflags_path)
+
+      warnings.filterwarnings('ignore', category=DeprecationWarning)
+
+      from closure_linter import checker, errors
+      from closure_linter.common import errorhandler
+
+    finally:
+      sys.path = old_path
+      warnings.filters = old_filters
+
+    class ErrorHandlerImpl(errorhandler.ErrorHandler):
+      """Filters out errors that don't apply to Chromium JavaScript code."""
+
+      def __init__(self):
+        self._errors = []
+
+      def HandleFile(self, filename, first_token):
+        self._filename = filename
+
+      def HandleError(self, error):
+        if (self._valid(error)):
+          error.filename = self._filename
+          self._errors.append(error)
+
+      def GetErrors(self):
+        return self._errors
+
+      def HasErrors(self):
+        return bool(self._errors)
+
+      def _valid(self, error):
+        """Check whether an error is valid. Most errors are valid, with a few
+           exceptions which are listed here.
+        """
+
+        is_grit_statement = bool(
+            re.search("</?(include|if)", error.token.line))
+
+        return not is_grit_statement and error.code not in [
+            errors.JSDOC_ILLEGAL_QUESTION_WITH_PIPE,
+            errors.JSDOC_TAG_DESCRIPTION_ENDS_WITH_INVALID_CHARACTER,
+            errors.MISSING_JSDOC_TAG_THIS,
+        ]
+
+    results = []
+
+    try:
+      affected_files = self.input_api.AffectedFiles(
+          file_filter=self.file_filter,
+          include_deletes=False)
+    except:
+      affected_files = []
+
+    def ShouldCheck(f):
+      if f.filename.endswith('.js'):
+        return True
+      if f.filename.endswith('.html'):
+        return True
+      return False
+
+    affected_js_files = filter(ShouldCheck, affected_files)
+    for f in affected_js_files:
+      error_lines = []
+
+      for i, line in enumerate(f.contents_as_lines, start=1):
+        error_lines += filter(None, [
+            self.ConstCheck(i, line),
+        ])
+
+      # Use closure_linter to check for several different errors
+      import gflags as flags
+      flags.FLAGS.strict = True
+      error_handler = ErrorHandlerImpl()
+      js_checker = checker.JavaScriptStyleChecker(error_handler)
+      js_checker.Check(os.path.join(
+          self.input_api.repository_root,
+          f.filename))
+
+      for error in error_handler.GetErrors():
+        highlight = self.error_highlight(
+            error.token.start_index, error.token.length)
+        error_msg = '  line %d: E%04d: %s\n%s\n%s' % (
+            error.token.line_number,
+            error.code,
+            error.message,
+            error.token.line.rstrip(),
+            highlight)
+        error_lines.append(error_msg)
+
+      if error_lines:
+        error_lines = [
+            'Found JavaScript style violations in %s:' %
+            f.filename] + error_lines
+        results.append(self._makeErrorOrWarning(
+            '\n'.join(error_lines), f.filename))
+
+    return results
+
+
+def RunChecks(input_api):
+  return JSChecker(input_api).RunChecks()
diff --git a/trace-viewer/hooks/pre_commit b/trace-viewer/hooks/pre_commit
new file mode 100755
index 0000000..d029fff
--- /dev/null
+++ b/trace-viewer/hooks/pre_commit
@@ -0,0 +1,16 @@
+#!/usr/bin/env python
+# Copyright (c) 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import os
+import sys
+
+if __name__ == '__main__':
+  f = __file__
+  if os.path.islink(f):
+    f = os.path.abspath(os.path.join(os.path.dirname(f), os.readlink(f)))
+  top_dir = os.path.abspath(os.path.join(os.path.dirname(f), '..'))
+  sys.path.append(top_dir)
+  from hooks import pre_commit
+  sys.exit(pre_commit.Main(sys.argv[1:]))
diff --git a/trace-viewer/hooks/pre_commit.py b/trace-viewer/hooks/pre_commit.py
new file mode 100644
index 0000000..f73f306
--- /dev/null
+++ b/trace-viewer/hooks/pre_commit.py
@@ -0,0 +1,185 @@
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import os
+import re
+import subprocess
+import sys
+
+from trace_viewer import trace_viewer_project
+
+class AffectedFile(object):
+  def __init__(self, input_api, filename):
+    self._filename = filename
+    self._input_api = input_api
+    self._cached_contents = None
+    self._cached_changed_contents = None
+    self._cached_new_contents = None
+
+  def __repr__(self):
+    return self._filename
+
+  @property
+  def filename(self):
+    return self._filename
+
+  @property
+  def contents(self):
+    if self._cached_contents is None:
+      self._cached_contents = self._input_api._git(
+          ['show', ':%s' % self._filename])
+    return self._cached_contents
+
+  @property
+  def is_added(self):
+    return self.fileame in self._input_api.added_files
+
+  @property
+  def contents_as_lines(self):
+    """Returns an iterator over the lines in the new version of file.
+
+    The new version is the file in the user's workspace, i.e. the "right hand
+    side".
+
+    Contents will be empty if the file is a directory or does not exist.
+    Note: The carriage returns (LF or CR) are stripped off.
+    """
+    if self._cached_new_contents is None:
+      self._cached_new_contents = self.contents.splitlines()
+    return self._cached_new_contents[:]
+
+  @property
+  def changed_lines(self):
+    """Returns a list of tuples (line number, line text) of all new lines.
+
+     This relies on the scm diff output describing each changed code section
+     with a line of the form
+
+     ^@@ <old line num>,<old size> <new line num>,<new size> @@$
+    """
+    if self._cached_changed_contents is not None:
+      return self._cached_changed_contents[:]
+    self._cached_changed_contents = []
+    line_num = 0
+
+    for line in self.GenerateDiff().splitlines():
+      m = re.match(r'^@@ [0-9\,\+\-]+ \+([0-9]+)\,[0-9]+ @@', line)
+      if m:
+        line_num = int(m.groups(1)[0])
+        continue
+      if line.startswith('+') and not line.startswith('++'):
+        self._cached_changed_contents.append((line_num, line[1:]))
+      if not line.startswith('-'):
+        line_num += 1
+    return self._cached_changed_contents[:]
+
+  def GenerateDiff(self):
+    return self._input_api._git(['diff', '--cached', self.filename])
+
+
+class InputAPI(object):
+  def __init__(self, tvp):
+    self.DEFAULT_BLACK_LIST = []
+    self._tvp = tvp
+    self._filename_statuses = None
+    self._added_files = None
+
+  def _git(self, args):
+    assert isinstance(args, list)
+    args = ['git'] + args
+    p = subprocess.Popen(
+        args,
+        stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+        cwd=self.repository_root)
+    res = p.communicate()
+    if p.wait() != 0:
+      raise Exception(res[1])
+    return res[0]
+
+  @property
+  def repository_root(self):
+    return self._tvp.trace_viewer_path
+
+  @property
+  def added_files(self):
+    if not self._added_files:
+      self._added_files = set()
+      for filename, status_char in filename_statuses:
+        if status_char == 'A':
+          self._added_files.Add(filename)
+    return self._added_files
+
+  @property
+  def affected_files(self):
+    return self.AffectedFiles(include_deletes=True)
+
+  def AffectedFiles(self,
+                    include_deletes=False,
+                    file_filter=lambda t: True):
+    filename_statuses = self._GetFilenameStatuses()
+    for filename, status_char in filename_statuses:
+      if status_char == 'D':
+        if include_deletes:
+          if file_filter(filename):
+            yield AffectedFile(self, filename)
+      else:
+        if file_filter(filename):
+          yield AffectedFile(self, filename)
+
+  def _GetFilenameStatuses(self):
+    if self._filename_statuses != None:
+      return self._filename_statuses
+
+    self._filename_statuses = []
+    stdout = self._git(['diff', '--cached', '--name-status'])
+    for line in stdout.split('\n'):
+      line = line.strip()
+      if len(line) == 0:
+        continue
+      m = re.match('([ACDMRTUXB])\s+(.+)', line)
+      if not m:
+        import pdb; pdb.set_trace()
+        assert m
+
+      status_char = m.group(1)
+      filename = m.group(2)
+      self._filename_statuses.append((filename, status_char))
+    return self._filename_statuses
+
+
+def RunChecks(input_api):
+  results = []
+
+  from hooks import pre_commit_checks
+  results += pre_commit_checks.RunChecks(input_api)
+
+  from trace_viewer.build import check_gyp
+  err = check_gyp.GypCheck()
+  if err:
+    results += [err]
+
+  from trace_viewer.build import check_gn
+  err = check_gn.GnCheck()
+  if err:
+    results += [err]
+
+  from trace_viewer.build import check_modules
+  err = check_modules.CheckModules()
+  if err:
+    results += [err]
+
+  from hooks import js_checks
+  results += js_checks.RunChecks(input_api)
+
+  return results
+
+
+def Main(args):
+  tvp = trace_viewer_project.TraceViewerProject()
+  input_api = InputAPI(tvp)
+  results = RunChecks(input_api)
+  print '\n\n'.join(results)
+
+  if len(results):
+    return 255
+  return 0
diff --git a/trace-viewer/hooks/pre_commit_checks.py b/trace-viewer/hooks/pre_commit_checks.py
new file mode 100644
index 0000000..987e3af
--- /dev/null
+++ b/trace-viewer/hooks/pre_commit_checks.py
@@ -0,0 +1,185 @@
+# Copyright (c) 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+import os
+import re
+import sys
+import time
+
+def _FormatError(msg, files):
+  return ('%s in these files:\n' % msg +
+      '\n'.join(['  ' + x for x in files])
+      )
+
+def _ReportErrorFileAndLine(filename, line_num, dummy_line):
+  """Default error formatter for _FindNewViolationsOfRule."""
+  return '%s:%s' % (filename, line_num)
+
+def _FindNewViolationsOfRule(callable_rule, input_api,
+                             error_formatter=_ReportErrorFileAndLine):
+  """Find all newly introduced violations of a per-line rule (a callable).
+
+  Arguments:
+    callable_rule: a callable taking a file extension and line of input and
+      returning True if the rule is satisfied and False if there was a problem.
+    input_api: object to enumerate the affected files.
+    source_file_filter: a filter to be passed to the input api.
+    error_formatter: a callable taking (filename, line_number, line) and
+      returning a formatted error string.
+
+  Returns:
+    A list of the newly-introduced violations reported by the rule.
+  """
+  errors = []
+  for f in input_api.AffectedFiles(include_deletes=False):
+    # For speed, we do two passes, checking first the full file.  Shelling out
+    # to the SCM to determine the changed region can be quite expensive on
+    # Win32.  Assuming that most files will be kept problem-free, we can
+    # skip the SCM operations most of the time.
+    extension = str(f.filename).rsplit('.', 1)[-1]
+    if all(callable_rule(extension, line) for line in f.contents_as_lines):
+      continue  # No violation found in full text: can skip considering diff.
+
+    for line_num, line in f.changed_lines:
+      if not callable_rule(extension, line):
+        errors.append(error_formatter(f.filename, line_num, line))
+
+  return errors
+
+def CheckCopyright(input_api):
+  project_name = 'Chromium'
+
+  current_year = int(time.strftime('%Y'))
+  allow_old_years=True
+  if allow_old_years:
+    allowed_years = (str(s) for s in reversed(xrange(2006, current_year + 1)))
+  else:
+    allowed_years = [str(current_year)]
+  years_re = '(' + '|'.join(allowed_years) + ')'
+
+  # The (c) is deprecated, but tolerate it until it's removed from all files.
+  non_html_license_header = (
+      r'.*? Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
+        r'All rights reserved\.\n'
+      r'.*? Use of this source code is governed by a BSD-style license that '
+        r'can be\n'
+      r'.*? found in the LICENSE file\.(?: \*/)?\n'
+  ) % {
+      'year': years_re,
+      'project': project_name,
+  }
+  non_html_license_re = re.compile(non_html_license_header, re.MULTILINE)
+
+  html_license_header = (
+      r'^Copyright (\(c\) )?%(year)s The %(project)s Authors\. '
+        r'All rights reserved\.\n'
+      r'Use of this source code is governed by a BSD-style license that '
+        r'can be\n'
+      r'found in the LICENSE file\.(?: \*/)?\n'
+  ) % {
+      'year': years_re,
+      'project': project_name,
+  }
+  html_license_re = re.compile(html_license_header, re.MULTILINE)
+
+  sources = list(input_api.AffectedFiles(include_deletes=False))
+
+  html_sources = [f for f in sources
+                  if os.path.splitext(f.filename)[1] == '.html']
+  non_html_sources = [f for f in sources
+                      if os.path.splitext(f.filename)[1] != '.html']
+
+  results = []
+  results += _Check(html_license_re, html_sources)
+  results += _Check(non_html_license_re, non_html_sources)
+  return results
+
+def _Check(license_re, sources):
+  bad_files = []
+  for f in sources:
+    contents = f.contents
+    if not license_re.search(contents):
+      bad_files.append(f.filename)
+  if bad_files:
+    return [_FormatError(
+        'License must match:\n%s\n' % license_re.pattern +
+        'Found a bad license header',
+        bad_files)]
+  return []
+
+def CheckLongLines(input_api, maxlen=80):
+  """Checks that there aren't any lines longer than maxlen characters in any of
+  the text files to be submitted.
+  """
+  maxlens = {
+      '': maxlen,
+  }
+
+  # Language specific exceptions to max line length.
+  # '.h' is considered an obj-c file extension, since OBJC_EXCEPTIONS are a
+  # superset of CPP_EXCEPTIONS.
+  CPP_FILE_EXTS = ('c', 'cc')
+  CPP_EXCEPTIONS = ('#define', '#endif', '#if', '#include', '#pragma')
+  JAVA_FILE_EXTS = ('java',)
+  JAVA_EXCEPTIONS = ('import ', 'package ')
+  OBJC_FILE_EXTS = ('h', 'm', 'mm')
+  OBJC_EXCEPTIONS = ('#define', '#endif', '#if', '#import', '#include',
+                     '#pragma')
+
+  LANGUAGE_EXCEPTIONS = [
+    (CPP_FILE_EXTS, CPP_EXCEPTIONS),
+    (JAVA_FILE_EXTS, JAVA_EXCEPTIONS),
+    (OBJC_FILE_EXTS, OBJC_EXCEPTIONS),
+  ]
+
+  def no_long_lines(file_extension, line):
+    # Check for language specific exceptions.
+    if any(file_extension in exts and line.startswith(exceptions)
+           for exts, exceptions in LANGUAGE_EXCEPTIONS):
+      return True
+
+    file_maxlen = maxlens.get(file_extension, maxlens[''])
+    # Stupidly long symbols that needs to be worked around if takes 66% of line.
+    long_symbol = file_maxlen * 2 / 3
+    # Hard line length limit at 50% more.
+    extra_maxlen = file_maxlen * 3 / 2
+
+    line_len = len(line)
+    if line_len <= file_maxlen:
+      return True
+
+    if '@suppress longLineCheck' in line:
+      return True
+
+    if line_len > extra_maxlen:
+      return False
+
+    if any((url in line) for url in ('file://', 'http://', 'https://')):
+      return True
+
+    if 'url(' in line and file_extension == 'css':
+      return True
+
+    if '<include' in line and file_extension in ('css', 'html', 'js'):
+      return True
+
+    return re.match(
+        r'.*[A-Za-z][A-Za-z_0-9]{%d,}.*' % long_symbol, line)
+
+  def format_error(filename, line_num, line):
+    return '%s, line %s, %s chars' % (filename, line_num, len(line))
+
+  errors = _FindNewViolationsOfRule(no_long_lines, input_api,
+                                    error_formatter=format_error)
+  if errors:
+    return [_FormatError(
+        'Found lines longer than %s characters' % maxlen,
+        errors)]
+  else:
+    return []
+
+def RunChecks(input_api):
+  results = []
+  results += CheckCopyright(input_api)
+  results += CheckLongLines(input_api)
+  return results