Correctly calculate multiple offscreen widget heights.

widgetHeight adds out-of-DOM widgets to the measure DIV to check their
height. Unfortunately, the check for "out-of-DOM" is an inadequate
heuristic. It only checks that the widget has a parentNode.

widgetHeight adds the widget to a new DIV which is in turn added to the
measure DIV. Later, the new DIV is removed from the measure DIV, but
the widget is not, in turn, removed from it. This is where the heuristic fails.

widgetHeight then attempts to measure the height of the out-of-DOM widget,
which is always 0.

I have also seen cases where the widget is attached to a line which is
out-of-DOM, but I haven't followed the logic through and written a test
case for it.
This commit is contained in:
Adam Ahmed 2014-06-03 09:16:42 +10:00 committed by Marijn Haverbeke
parent fe2bb260d9
commit 57e7ed7177
2 changed files with 31 additions and 10 deletions

View File

@ -4388,9 +4388,28 @@ window.CodeMirror = (function() {
regChange(this.cm, no, no + 1);
});
// A widget can be:
// - in the code itself (in DOM)
// - in the measure div (in DOM)
// - an orphan (not in DOM)
// - the descendant of an orphan (not in DOM)
// The last situation can happen if it was in the measure div and was removed.
// It can also happen when a line is removed from the DOM and the widget is still attached to it.
function widgetInEditorDOM(widget) {
var arbitraryWiggleRoom = 4; // maybe there are cases I don't know about. Maybe the DOM changes in future versions, ...
// can shortcut to the grandparent node safely without passing CodeMirror-lines or -code.
return widget.node && widget.node.parentNode && _inEditorDOM(widget.node.parentNode.parentNode, arbitraryWiggleRoom);
}
function _inEditorDOM(node, recurseLimit) {
return node ?
/CodeMirror-(lines|code)/.test(node.className) || (recurseLimit && _inEditorDOM(node.parentNode, recurseLimit - 1)) :
false;
}
function widgetHeight(widget) {
if (widget.height != null) return widget.height;
if (!widget.node.parentNode || widget.node.parentNode.nodeType != 1)
if (!widgetInEditorDOM(widget))
removeChildrenAndAdd(widget.cm.display.measure, elt("div", [widget.node], null, "position: relative"));
return widget.height = widget.node.offsetHeight;
}

View File

@ -3,15 +3,17 @@
namespace = "widget_";
function makeLineWidget(cm, height, line) {
var widgetEl = document.createElement('div');
widgetEl.style.height = height + 'px';
widgetEl.textContent = 'dummy text';
function makeLineWidget(cm, height) {
return function(line) {
var widgetEl = document.createElement('div');
widgetEl.style.height = height + 'px';
widgetEl.textContent = 'dummy text';
return {
el : widgetEl,
widget : cm.addLineWidget(line, widgetEl, {})
};
return {
el : widgetEl,
widget : cm.addLineWidget(line, widgetEl, {})
};
}
}
function each(arr, fn) {
@ -34,7 +36,7 @@
cm.scrollIntoView({ line : startingViewportLine, ch: 0 });
}
var widgetInfos = widgetAtLines.map(makeLineWidget.bind(null, cm, initialHeight));
var widgetInfos = widgetAtLines.map(makeLineWidget(cm, initialHeight));
var expectedHeightAfterAdd = startingDocHeight + (widgetInfos.length * initialHeight);
eq(expectedHeightAfterAdd, cm.getScrollInfo().height, 'addLineWidget(): widget height should be added to document height.');