From ece15b2a24acbbd9e68da239cdf54349793c266e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 14 Sep 2015 14:48:30 +0200 Subject: [PATCH] Recycle tile nodes (and descendants) --- spec/dom-elements-pool-spec.coffee | 41 ++++++++++++++++++++++++++ src/dom-elements-pool.coffee | 25 ++++++++++++++++ src/line-numbers-tile-component.coffee | 3 ++ src/lines-component.coffee | 5 +++- src/lines-tile-component.coffee | 37 +++++++++++------------ src/tiled-component.coffee | 4 +-- 6 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 spec/dom-elements-pool-spec.coffee create mode 100644 src/dom-elements-pool.coffee diff --git a/spec/dom-elements-pool-spec.coffee b/spec/dom-elements-pool-spec.coffee new file mode 100644 index 000000000..ee9c99062 --- /dev/null +++ b/spec/dom-elements-pool-spec.coffee @@ -0,0 +1,41 @@ +DomElementsPool = require '../src/dom-elements-pool' + +describe "DomElementsPool", -> + domElementsPool = null + + beforeEach -> + domElementsPool = new DomElementsPool + + it "creates new nodes until some of them are freed", -> + span1 = domElementsPool.build("span") + span2 = domElementsPool.build("span") + span3 = domElementsPool.build("span") + + expect(span1).not.toBe(span2) + expect(span2).not.toBe(span3) + + domElementsPool.free(span1) + domElementsPool.free(span2) + + expect(domElementsPool.build("span")).toBe(span2) + expect(domElementsPool.build("span")).toBe(span1) + + it "recursively frees a dom tree", -> + div = domElementsPool.build("div") + span1 = domElementsPool.build("span") + span2 = domElementsPool.build("span") + span3 = domElementsPool.build("span") + span4 = domElementsPool.build("span") + + div.appendChild(span1) + span1.appendChild(span2) + div.appendChild(span3) + span3.appendChild(span4) + + domElementsPool.freeElementAndDescendants(div) + + expect(domElementsPool.build("div")).toBe(div) + expect(domElementsPool.build("span")).toBe(span3) + expect(domElementsPool.build("span")).toBe(span4) + expect(domElementsPool.build("span")).toBe(span1) + expect(domElementsPool.build("span")).toBe(span2) diff --git a/src/dom-elements-pool.coffee b/src/dom-elements-pool.coffee new file mode 100644 index 000000000..482eb8417 --- /dev/null +++ b/src/dom-elements-pool.coffee @@ -0,0 +1,25 @@ +{toArray} = require 'underscore-plus' + +module.exports = +class DomElementsPool + constructor: -> + @freeElementsByTagName = {} + + build: (tagName, className, textContent) -> + element = @freeElementsByTagName[tagName]?.pop() + element ?= document.createElement(tagName) + element.className = className + element.textContent = textContent + element.removeAttribute("style") + element + + free: (element) -> + element.remove() + @freeElementsByTagName[element.tagName.toLowerCase()] ?= [] + @freeElementsByTagName[element.tagName.toLowerCase()].push(element) + + freeElementAndDescendants: (element) -> + for child in toArray(element.children) + @freeElementAndDescendants(child) + + @free(element) diff --git a/src/line-numbers-tile-component.coffee b/src/line-numbers-tile-component.coffee index 2ebccbc65..703054024 100644 --- a/src/line-numbers-tile-component.coffee +++ b/src/line-numbers-tile-component.coffee @@ -14,6 +14,9 @@ class LineNumbersTileComponent @domNode.style.display = "block" @domNode.style.top = 0 # Cover the space occupied by a dummy lineNumber + destroy: -> + @domNode.remove() + getDomNode: -> @domNode diff --git a/src/lines-component.coffee b/src/lines-component.coffee index 2b80235dc..d3d1f9b0a 100644 --- a/src/lines-component.coffee +++ b/src/lines-component.coffee @@ -3,6 +3,7 @@ CursorsComponent = require './cursors-component' LinesTileComponent = require './lines-tile-component' TiledComponent = require './tiled-component' +DomElementsPool = require './dom-elements-pool' DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0] @@ -23,6 +24,8 @@ class LinesComponent extends TiledComponent @cursorsComponent = new CursorsComponent @domNode.appendChild(@cursorsComponent.getDomNode()) + @elementsPool = new DomElementsPool + if @useShadowDOM insertionPoint = document.createElement('content') insertionPoint.setAttribute('select', '.overlayer') @@ -60,7 +63,7 @@ class LinesComponent extends TiledComponent @oldState.indentGuidesVisible = @newState.indentGuidesVisible - buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter}) + buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @elementsPool}) buildEmptyState: -> {tiles: {}} diff --git a/src/lines-tile-component.coffee b/src/lines-tile-component.coffee index 131db79f0..da653d279 100644 --- a/src/lines-tile-component.coffee +++ b/src/lines-tile-component.coffee @@ -14,13 +14,13 @@ cloneObject = (object) -> module.exports = class LinesTileComponent - constructor: ({@presenter, @id}) -> + constructor: ({@presenter, @id, @elementsPool}) -> @tokenIterator = new TokenIterator @measuredLines = new Set @lineNodesByLineId = {} @screenRowsByLineId = {} @lineIdsByScreenRow = {} - @domNode = document.createElement("div") + @domNode = @elementsPool.build("div", "tile") @domNode.classList.add("tile") @domNode.style.position = "absolute" @domNode.style.display = "block" @@ -28,6 +28,9 @@ class LinesTileComponent @highlightsComponent = new HighlightsComponent @domNode.appendChild(@highlightsComponent.getDomNode()) + destroy: -> + @elementsPool.freeElementAndDescendants(@domNode) + getDomNode: -> @domNode @@ -77,7 +80,7 @@ class LinesTileComponent return removeLineNode: (id) -> - @lineNodesByLineId[id].remove() + @elementsPool.freeElementAndDescendants(@lineNodesByLineId[id]) delete @lineNodesByLineId[id] delete @lineIdsByScreenRow[@screenRowsByLineId[id]] delete @screenRowsByLineId[id] @@ -116,7 +119,7 @@ class LinesTileComponent {width} = @newState {screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id] - lineNode = @buildElement("div", "line") + lineNode = @elementsPool.build("div", "line") if decorationClasses? for decorationClass in decorationClasses lineNode.classList.add(decorationClass) @@ -131,15 +134,9 @@ class LinesTileComponent else @appendLineInnerNodes(id, lineNode) - lineNode.appendChild(@buildElement("span", "fold-marker")) if fold + lineNode.appendChild(@elementsPool.build("span", "fold-marker")) if fold lineNode - buildElement: (name, className, textContent) -> - element = document.createElement(name) - element.className = className if className? - element.textContent = textContent if textContent? - element - appendEmptyLineInnerNodes: (id, lineNode) -> {indentGuidesVisible} = @newState {indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id] @@ -148,11 +145,11 @@ class LinesTileComponent invisibleIndex = 0 lineHTML = '' for i in [0...indentLevel] - indentGuide = @buildElement("span", "indent-guide") + indentGuide = @elementsPool.build("span", "indent-guide") for j in [0...tabLength] if invisible = endOfLineInvisibles?[invisibleIndex++] indentGuide.appendChild( - @buildElement("span", "invisible-character", invisible) + @elementsPool.build("span", "invisible-character", invisible) ) else indentGuide.insertAdjacentText("beforeend", " ") @@ -161,7 +158,7 @@ class LinesTileComponent while invisibleIndex < endOfLineInvisibles?.length invisible = endOfLineInvisibles[invisibleIndex++] lineNode.appendChild( - @buildElement("span", "invisible-character", invisible) + @elementsPool.build("span", "invisible-character", invisible) ) else unless @appendEndOfLineNodes(id, lineNode) @@ -181,7 +178,7 @@ class LinesTileComponent openScopeNode = openScopeNode.parentElement for scope in @tokenIterator.getScopeStarts() - newScopeNode = @buildElement("span", scope.replace(/\.+/g, ' ')) + newScopeNode = @elementsPool.build("span", scope.replace(/\.+/g, ' ')) openScopeNode.appendChild(newScopeNode) openScopeNode = newScopeNode @@ -214,7 +211,7 @@ class LinesTileComponent appendToken: (scopeNode, tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) -> if isHardTab - hardTab = @buildElement("span", "hard-tab", tokenText) + hardTab = @elementsPool.build("span", "hard-tab", tokenText) hardTab.classList.add("leading-whitespace") if firstNonWhitespaceIndex? hardTab.classList.add("trailing-whitespace") if firstTrailingWhitespaceIndex? hardTab.classList.add("indent-guide") if hasIndentGuide @@ -229,7 +226,7 @@ class LinesTileComponent trailingWhitespaceNode = null if firstNonWhitespaceIndex? - leadingWhitespaceNode = @buildElement( + leadingWhitespaceNode = @elementsPool.build( "span", "leading-whitespace", tokenText.substring(0, firstNonWhitespaceIndex) @@ -242,7 +239,7 @@ class LinesTileComponent if firstTrailingWhitespaceIndex? tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0 - trailingWhitespaceNode = @buildElement( + trailingWhitespaceNode = @elementsPool.build( "span", "trailing-whitespace", tokenText.substring(firstTrailingWhitespaceIndex) @@ -257,7 +254,7 @@ class LinesTileComponent if tokenText.length > MaxTokenLength while startIndex < endIndex text = @sliceText(tokenText, startIndex, startIndex + MaxTokenLength) - scopeNode.appendChild(@buildElement("span", null, text)) + scopeNode.appendChild(@elementsPool.build("span", null, text)) startIndex += MaxTokenLength else scopeNode.insertAdjacentText("beforeend", @sliceText(tokenText, startIndex, endIndex)) @@ -277,7 +274,7 @@ class LinesTileComponent for invisible in endOfLineInvisibles hasInvisibles = true lineNode.appendChild( - @buildElement("span", "invisible-character", invisible) + @elementsPool.build("span", "invisible-character", invisible) ) hasInvisibles diff --git a/src/tiled-component.coffee b/src/tiled-component.coffee index 33719dda5..5fecb86f4 100644 --- a/src/tiled-component.coffee +++ b/src/tiled-component.coffee @@ -21,9 +21,7 @@ class TiledComponent return removeTileNode: (tileRow) -> - node = @componentsByTileId[tileRow].getDomNode() - - node.remove() + @componentsByTileId[tileRow].destroy() delete @componentsByTileId[tileRow] delete @oldState.tiles[tileRow]