From f4cedacc7bf8209aa43c6d1407acf999ec64475d Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 6 Jun 2016 15:14:36 +0100 Subject: [PATCH] Optimise email CSS for speed with Premailer Remove all descendant selectors from the push email styling, to drastically reduce CPU time when inlining the CSS for syntax-highlighted diffs. Background: Premailer is a Ruby gem that inlines CSS styles from an external stylesheet before emails are sent, so that they are compatible with Gmail. At a high level, it parses the CSS files it finds, and parses the email body with Nokogiri. It then loops through the selectors in the CSS, using Nokogiri to find matching elements, and adds inline styles. (It does more than this, like merging styles applied to the same element, but that's not relevant to this issue.) Nokogiri converts CSS selectors to XPath first, like so: Nokogiri::CSS.xpath_for('foo bar') # => ["//foo//bar"] On documents with high node counts (say, a syntax-highlighted copy of jQuery), having both descendant selectors is very expensive. Both `//foo/bar` and `//bar` will be much more efficient, although neither are directly equivalent. An example, on a document containing two syntax-highlighted copies of jQuery: Benchmark.realtime { p doc.search('.o').count } # 9476 # => 0.3462457580026239 Benchmark.realtime { p doc.search('.code.white .o').count } # 9476 # => 85.51952634402551 The performance is similar for selectors which _don't_ match any elements, and as Premailer loops through all the available selectors, we want to avoid all descendant selectors in push emails. Because of the theming support in the web UI, all syntax highlighting selectors are descendant selectors of classes like `.code.white` or `.code.monokai`. There are over 60 CSS classes for syntax highlighting styles alone, all of which are expressed in the inefficient form above. In emails we always use the white theme, and were reusing the same CSS file. But in emails, we don't need to descend from `.code.white` as that will always be the theme, and we can also remove some other selectors that are only applicable to the web UI. For the remaining descendant selectors, we can convert them to child selectors, type selectors, or class selectors as appropriate. As in the example above, having no descendant selectors at all in the push email CSS can provide a drastic (and surprising) performance improvement. --- .../mailers/repository_push_email.scss | 183 +++++++++++++++--- app/assets/stylesheets/notify.scss | 16 +- 2 files changed, 169 insertions(+), 30 deletions(-) diff --git a/app/assets/stylesheets/mailers/repository_push_email.scss b/app/assets/stylesheets/mailers/repository_push_email.scss index 001994db97b..7f645d3089d 100644 --- a/app/assets/stylesheets/mailers/repository_push_email.scss +++ b/app/assets/stylesheets/mailers/repository_push_email.scss @@ -1,5 +1,15 @@ @import "framework/variables"; +// This file is largely copied from `highlight/white.scss`, but modified to +// avoid all descendant selectors (`table td`). This is because the CSS inlining +// we use performs dramatically worse on descendant selectors than the +// alternatives. +// +// +// DO NOT ADD ANY DESCENDANT SELECTORS TO THIS FILE. Instead, use (in order of +// preference): plain class selectors, type (element name) selectors, or +// explicit child selectors. + table.code { width: 100%; font-family: monospace; @@ -11,33 +21,162 @@ table.code { -premailer-cellspacing: 0; -premailer-width: 100%; - td { + > tr > td { line-height: $code_line_height; font-family: monospace; font-size: $code_font_size; - } - td.diff-line-num { - margin: 0; - padding: 0; - border: none; - background: $background-color; - color: rgba(0, 0, 0, 0.3); - padding: 0 5px; - border-right: 1px solid $border-color; - text-align: right; - min-width: 35px; - max-width: 50px; - width: 35px; - } + &.diff-line-num { + margin: 0; + padding: 0; + border: none; + padding: 0 5px; + border-right: 1px solid; + text-align: right; + min-width: 35px; + max-width: 50px; + width: 35px; + } - td.line_content { - display: block; - margin: 0; - padding: 0 0.5em; - border: none; - white-space: pre; + &.line_content { + display: block; + margin: 0; + padding: 0 0.5em; + border: none; + white-space: pre; + } } } -@import "highlight/white"; +.line-numbers, .diff-line-num { + background-color: $background-color; +} + +.diff-line-num, .diff-line-num a { + color: $black-transparent; +} + +pre.code, .diff-line-num { + border-color: $table-border-gray; +} + +.code.white, pre.code, .line_content { + background-color: #fff; + color: #333; +} + +.diff-line-num { + &.old { + background-color: $line-number-old; + border-color: $line-removed-dark; + } + + &.new { + background-color: $line-number-new; + border-color: $line-added-dark; + } + + &.hll:not(.empty-cell) { + background-color: $line-number-select; + border-color: $line-select-yellow-dark; + } +} + +.line_content { + &.old { + background-color: $line-removed; + + > .line > span.idiff, > .line > span > span.idiff { + background-color: $line-removed-dark; + } + } + + &.new { + background-color: $line-added; + + > .line > span.idiff, > .line > span > span.idiff { + background-color: $line-added-dark; + } + } + + &.match { + color: $black-transparent; + background-color: $match-line; + } + + &.hll:not(.empty-cell) { + background-color: $line-select-yellow; + } +} + +pre > .hll { + background-color: #f8eec7 !important; +} + +span.highlight_word { + background-color: #fafe3d !important; +} + +.hll { background-color: #f8f8f8 } +.c { color: #998; font-style: italic; } +.err { color: #a61717; background-color: #e3d2d2; } +.k { font-weight: bold; } +.o { font-weight: bold; } +.cm { color: #998; font-style: italic; } +.cp { color: #999; font-weight: bold; } +.c1 { color: #998; font-style: italic; } +.cs { color: #999; font-weight: bold; font-style: italic; } +.gd { color: #000; background-color: #fdd; } +.gd .x { color: #000; background-color: #faa; } +.ge { font-style: italic; } +.gr { color: #a00; } +.gh { color: #999; } +.gi { color: #000; background-color: #dfd; } +.gi .x { color: #000; background-color: #afa; } +.go { color: #888; } +.gp { color: #555; } +.gs { font-weight: bold; } +.gu { color: #800080; font-weight: bold; } +.gt { color: #a00; } +.kc { font-weight: bold; } +.kd { font-weight: bold; } +.kn { font-weight: bold; } +.kp { font-weight: bold; } +.kr { font-weight: bold; } +.kt { color: #458; font-weight: bold; } +.m { color: #099; } +.s { color: #d14; } +.n { color: #333; } +.na { color: teal; } +.nb { color: #0086b3; } +.nc { color: #458; font-weight: bold; } +.no { color: teal; } +.ni { color: purple; } +.ne { color: #900; font-weight: bold; } +.nf { color: #900; font-weight: bold; } +.nn { color: #555; } +.nt { color: navy; } +.nv { color: teal; } +.ow { font-weight: bold; } +.w { color: #bbb; } +.mf { color: #099; } +.mh { color: #099; } +.mi { color: #099; } +.mo { color: #099; } +.sb { color: #d14; } +.sc { color: #d14; } +.sd { color: #d14; } +.s2 { color: #d14; } +.se { color: #d14; } +.sh { color: #d14; } +.si { color: #d14; } +.sx { color: #d14; } +.sr { color: #009926; } +.s1 { color: #d14; } +.ss { color: #990073; } +.bp { color: #999; } +.vc { color: teal; } +.vg { color: teal; } +.vi { color: teal; } +.il { color: #099; } +.gc { color: #999; background-color: #eaf2f5; } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index 0a13a7e0b54..fc12964872d 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -6,19 +6,19 @@ p.details { font-style: italic; color: #777 } -.footer p { +.footer > p { font-size: small; color: #777 } pre.commit-message { white-space: pre-wrap; } -.file-stats a { +.file-stats > a { text-decoration: none; -} -.file-stats .new-file { - color: #090; -} -.file-stats .deleted-file { - color: #b00; + > .new-file { + color: #090; + } + > .deleted-file { + color: #b00; + } }