Improves visible tabs rendering

This change reverts visible tabs to use the » character. A few different
approaches have been used for rendering these tab indicators:

I:  Before the Annotation Pipeline, tab indicators were configured by a
    class that was optionally applied to tab elements by the diff
    builder. The class was guarded by the show_tabs preference and a CSS
    rule created a `::before` pseudo element to insert the character.
    (Thus also preventing the » from being copyable text.)

II: When the Annotation Pipeline was implemented, the ordering of layers
    called for intraline difference elements being rendered *inside* tab
    indicators. As a result, the » indicator would sometimes have a
    different background than the intraline difference, looking sloppy.

    To solve this, the pseudo element was positioned using absolute,
    allowing the pseudo element to consume no horizontal space and and
    the intraline background to extend across the entire tab.

III:When performance tuning, it was discovered that the
    absolute-positioned tab indicators were positioned incorrectly when
    GPU acceleration was hinted for the diff, so the containing tab
    elements were made relative.

IV: Continuing performance tuning, the tab indicators seemed to slow
    scrolling on very large diffs with tabs. It was mistakenly assumed
    (by me) that this was related to the pseudo-elements when it was
    actually caused by the thousands of positioning contexts they
    created using relative and absolute.

    Instead they were modified to use strike-through instead of a pseudo
    element, which was more-performant, but less-usable (it looked bad).

With this change, we roll-back the clock a little and solve a core
problem: namely that tab indicators were not annotated inside the
intraline differences. Fixing that, positioning tricks are no-longer
needed to make the background look right.

To do this, we decouple the tab elements (which control tab width) from
the tab indicators (which optionally make tabs visible). This is done
using an annotation layer that inserts tab indicator elements wherever
a tab character is used in the source content, and it does so after
intraline differences have been applied.

Bug: Issue 4441
Change-Id: I4fea2a3a20a7039bfeb746bd1e1c1004e43c4801
This commit is contained in:
Wyatt Allen
2016-08-25 11:31:42 -07:00
parent fc090c8f36
commit be0142ca03
4 changed files with 145 additions and 9 deletions

View File

@@ -77,6 +77,7 @@ limitations under the License.
_builder: Object,
_groups: Array,
_layers: Array,
_showTabs: Boolean,
},
get diffElement() {
@@ -92,6 +93,7 @@ limitations under the License.
this._layers = [
this.$.syntaxLayer,
this._createIntralineLayer(),
this._createTabIndicatorLayer(),
this.$.rangeLayer,
];
@@ -102,6 +104,7 @@ limitations under the License.
render: function(comments, prefs) {
this.$.syntaxLayer.enabled = prefs.syntax_highlighting;
this._showTabs = !!prefs.show_tabs;
// Stop the processor (if it's running).
this.$.processor.cancel();
@@ -330,6 +333,31 @@ limitations under the License.
};
},
_createTabIndicatorLayer: function() {
var show = (function() { return this._showTabs; }).bind(this);
return {
addListener: function() {},
annotate: function(el, line) {
// If visible tabs are disabled, do nothing.
if (!show()) { return; }
// Find and annotate the locations of tabs.
var split = line.text.split('\t');
if (!split) { return; }
for (var i = 0, pos = 0; i < split.length - 1; i++) {
// Skip forward by the length of the content
pos += split[i].length;
GrAnnotation.annotateElement(el, pos, 1,
'style-scope gr-diff tab-indicator');
// Skip forward by one tab character.
pos++;
}
},
};
},
/**
* In pages with large diffs, creating the first comment thread can be
* slow because nested Polymer elements (particularly

View File

@@ -493,7 +493,7 @@
for (var i = 0; i < split.length - 1; i++) {
offset += split[i].length;
width = tabSize - (offset % tabSize);
result += split[i] + this._getTabWrapper(width, this._prefs.show_tabs);
result += split[i] + this._getTabWrapper(width);
offset += width;
}
if (split.length) {
@@ -503,7 +503,7 @@
return result;
};
GrDiffBuilder.prototype._getTabWrapper = function(tabSize, showTabs) {
GrDiffBuilder.prototype._getTabWrapper = function(tabSize) {
// Force this to be a number to prevent arbitrary injection.
tabSize = +tabSize;
if (isNaN(tabSize)) {
@@ -511,9 +511,6 @@
}
var str = '<span class="style-scope gr-diff tab ';
if (showTabs) {
str += 'withIndicator';
}
str += '" style="';
// TODO(andybons): CSS tab-size is not supported in IE.
str += 'tab-size:' + tabSize + ';';

View File

@@ -414,6 +414,117 @@ limitations under the License.
});
});
suite('tab indicators', function() {
var sandbox;
var element;
var layer;
setup(function() {
sandbox = sinon.sandbox.create();
element = fixture('basic');
element._showTabs = true;
layer = element._createTabIndicatorLayer();
});
teardown(function() {
sandbox.restore();
});
test('does nothing with empty line', function() {
var line = {text: ''};
var el = document.createElement('div');
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.isFalse(annotateElementStub.called);
});
test('does nothing with no tabs', function() {
var str = 'lorem ipsum no tabs';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.isFalse(annotateElementStub.called);
});
test('annotates tab at beginning', function() {
var str = '\tlorem upsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.equal(annotateElementStub.callCount, 1);
var args = annotateElementStub.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], 0, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
});
test('does not annotate when disabled', function() {
element._showTabs = false;
var str = '\tlorem upsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.isFalse(annotateElementStub.called);
});
test('annotates multiple in beginning', function() {
var str = '\t\tlorem upsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.equal(annotateElementStub.callCount, 2);
var args = annotateElementStub.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], 0, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
args = annotateElementStub.getCalls()[1].args;
assert.equal(args[0], el);
assert.equal(args[1], 1, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
});
test('annotates intermediate tabs', function() {
var str = 'lorem\tupsum';
var line = {text: str};
var el = document.createElement('div');
el.textContent = str;
var annotateElementStub = sandbox.stub(GrAnnotation, 'annotateElement');
layer.annotate(el, line);
assert.equal(annotateElementStub.callCount, 1);
var args = annotateElementStub.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], 5, 'offset of tab indicator');
assert.equal(args[2], 1, 'length of tab indicator');
assert.include(args[3], 'tab-indicator');
});
});
suite('rendering', function() {
var content;
var outputEl;

View File

@@ -152,11 +152,11 @@ limitations under the License.
}
.tab {
display: inline-block;
position: relative;
}
.tab.withIndicator {
color: #D68E47;
text-decoration: line-through;
.tab-indicator:before {
color: #C62828;
/* >> character */
content: '\00BB';
}
</style>
<style include="gr-theme-default"></style>