Modified: trunk/Source/WebCore/inspector/front-end/DefaultTextEditor.js (134243 => 134244)
--- trunk/Source/WebCore/inspector/front-end/DefaultTextEditor.js 2012-11-12 16:13:01 UTC (rev 134243)
+++ trunk/Source/WebCore/inspector/front-end/DefaultTextEditor.js 2012-11-12 16:20:49 UTC (rev 134244)
@@ -877,7 +877,7 @@
{
delete this._repaintAllTimer;
- if (this._paintCoalescingLevel)
+ if (this._paintCoalescingLevel || this._dirtyLines)
return;
var visibleFrom = this.element.scrollTop;
@@ -1266,9 +1266,13 @@
this.element.addEventListener("scroll", this._scroll.bind(this), false);
this.element.addEventListener("focus", this._handleElementFocus.bind(this), false);
- this._mutationObserver = new WebKitMutationObserver(this._handleMutations.bind(this));
- this._mutationObserver.options = { subtree: true, childList: true, characterData: true };
- this._mutationObserver.observe(this._container, this._mutationObserver.options);
+ // OPTIMIZATION. It is very expensive to listen to the DOM mutation events, thus we remove the
+ // listeners whenever we do any internal DOM manipulations (such as expand/collapse line rows)
+ // and set the listeners back when we are finished.
+ this._handleDOMUpdatesCallback = this._handleDOMUpdates.bind(this);
+ this._container.addEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false);
+ this._container.addEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false);
+ this._container.addEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false);
this._freeCachedElements();
this._buildChunks();
@@ -1399,13 +1403,16 @@
var markedLine = this._rangeToMark.startLine;
delete this._rangeToMark;
// Remove the marked region immediately.
- this.beginDomUpdates();
- var chunk = this.chunkForLine(markedLine);
- var wasExpanded = chunk.expanded;
- chunk.expanded = false;
- chunk.updateCollapsedLineRow();
- chunk.expanded = wasExpanded;
- this.endDomUpdates();
+ if (!this._dirtyLines) {
+ this.beginDomUpdates();
+ var chunk = this.chunkForLine(markedLine);
+ var wasExpanded = chunk.expanded;
+ chunk.expanded = false;
+ chunk.updateCollapsedLineRow();
+ chunk.expanded = wasExpanded;
+ this.endDomUpdates();
+ } else
+ this._paintLines(markedLine, markedLine + 1);
}
if (range) {
@@ -1457,6 +1464,9 @@
if (this.readOnly())
return false;
+ if (this._dirtyLines)
+ return false;
+
this.beginUpdates();
function before()
@@ -1488,6 +1498,9 @@
if (this.readOnly())
return false;
+ if (this._dirtyLines)
+ return false;
+
var selection = this._getSelection();
if (!selection)
return false;
@@ -1593,6 +1606,9 @@
if (this.readOnly())
return false;
+ if (this._dirtyLines)
+ return false;
+
var range = this._getSelection();
if (!range)
return false;
@@ -1652,16 +1668,22 @@
beginDomUpdates: function()
{
- if (!this._domUpdateCoalescingLevel)
- this._mutationObserver.disconnect();
WebInspector.TextEditorChunkedPanel.prototype.beginDomUpdates.call(this);
+ if (this._domUpdateCoalescingLevel === 1) {
+ this._container.removeEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false);
+ this._container.removeEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false);
+ this._container.removeEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false);
+ }
},
endDomUpdates: function()
{
WebInspector.TextEditorChunkedPanel.prototype.endDomUpdates.call(this);
- if (!this._domUpdateCoalescingLevel)
- this._mutationObserver.observe(this._container, this._mutationObserver.options);
+ if (this._domUpdateCoalescingLevel === 0) {
+ this._container.addEventListener("DOMCharacterDataModified", this._handleDOMUpdatesCallback, false);
+ this._container.addEventListener("DOMNodeInserted", this._handleDOMUpdatesCallback, false);
+ this._container.addEventListener("DOMSubtreeModified", this._handleDOMUpdatesCallback, false);
+ }
},
_buildChunks: function()
@@ -1757,7 +1779,7 @@
return;
// Reschedule the timer if we can not paint the lines yet, or the user is scrolling.
- if (this._repaintAllTimer) {
+ if (this._dirtyLines || this._repaintAllTimer) {
this._paintScheduledLinesTimer = setTimeout(this._paintScheduledLines.bind(this), 50);
return;
}
@@ -1816,7 +1838,7 @@
var invisibleLineRows = [];
for (var i = 0; i < lineChunks.length; ++i) {
var lineChunk = lineChunks[i];
- if (this._scheduledPaintLines) {
+ if (this._dirtyLines || this._scheduledPaintLines) {
this._schedulePaintLines(lineChunk.startLine, lineChunk.endLine);
continue;
}
@@ -1856,6 +1878,10 @@
_paintLine: function(lineRow)
{
var lineNumber = lineRow.lineNumber;
+ if (this._dirtyLines) {
+ this._schedulePaintLines(lineNumber, lineNumber + 1);
+ return;
+ }
this.beginDomUpdates();
try {
@@ -2077,8 +2103,6 @@
var span = this._cachedSpans.pop() || document.createElement("span");
span.className = "webkit-" + className;
- if (false) // For paint debugging.
- span.addStyleClass("debug-fadeout");
span.textContent = content;
element.insertBefore(span, oldChild);
if (!("spans" in element))
@@ -2126,88 +2150,29 @@
return span;
},
- /**
- * @param {Array.<WebKitMutation>} mutations
- */
- _handleMutations: function(mutations)
+ _handleDOMUpdates: function(e)
{
if (this._domUpdateCoalescingLevel)
return;
- if (this._readOnly)
+ var target = e.target;
+ if (target === this._container)
return;
- // Annihilate noop BR addition + removal that takes place upon line removal.
- var filteredMutations = mutations.slice();
- var addedBRs = new Map();
- for (var i = 0; i < mutations.length; ++i) {
- var mutation = mutations[i];
- if (mutation.type !== "childList")
- continue;
- if (mutation.addedNodes.length === 1 && mutation.addedNodes[0].nodeName === "BR")
- addedBRs.put(mutation.addedNodes[0], mutation);
- else if (mutation.removedNodes.length === 1 && mutation.removedNodes[0].nodeName === "BR") {
- var noopMutation = addedBRs.get(mutation.removedNodes[0]);
- if (noopMutation) {
- filteredMutations.remove(mutation);
- filteredMutations.remove(noopMutation);
- }
- }
- }
-
- var dirtyLines;
- for (var i = 0; i < filteredMutations.length; ++i) {
- var mutation = filteredMutations[i];
- var changedNodes = [];
- if (mutation.type === "childList" && mutation.addedNodes.length)
- changedNodes = Array.prototype.slice.call(mutation.addedNodes);
- else if (mutation.type === "childList" && mutation.removedNodes.length)
- changedNodes = Array.prototype.slice.call(mutation.removedNodes);
- changedNodes.push(mutation.target);
-
- for (var j = 0; j < changedNodes.length; ++j) {
- var lines = this._collectDirtyLines(mutation, changedNodes[j]);
- if (!lines)
- continue;
- if (!dirtyLines) {
- dirtyLines = lines;
- continue;
- }
- dirtyLines.start = Math.min(dirtyLines.start, lines.start);
- dirtyLines.end = Math.max(dirtyLines.end, lines.end);
- }
- }
-
- if (dirtyLines) {
- delete this._rangeToMark;
- this._applyDomUpdates(dirtyLines);
- }
-
- // Debug check for validating whether text model matches the DOM representation.
- // console.assert(this.element.innerText === this._textModel.text() + "\n", "DOM does not match model.");
- },
-
- /**
- * @param {WebKitMutation} mutation
- * @param {Node} target
- * @return {?Object}
- */
- _collectDirtyLines: function(mutation, target)
- {
var lineRow = this._enclosingLineRowOrSelf(target);
if (!lineRow)
- return null;
+ return;
if (lineRow.decorationsElement && lineRow.decorationsElement.isSelfOrAncestor(target)) {
if (this._syncDecorationsForLineListener)
this._syncDecorationsForLineListener(lineRow.lineNumber);
- return null;
+ return;
}
if (this._readOnly)
- return null;
+ return;
- if (target === lineRow && mutation.type === "childList" && mutation.addedNodes.length) {
+ if (target === lineRow && e.type === "DOMNodeInserted") {
// Ensure that the newly inserted line row has no lineNumber.
delete lineRow.lineNumber;
}
@@ -2227,14 +2192,31 @@
break;
}
}
- return { start: startLine, end: endLine };
+
+ if (this._dirtyLines) {
+ this._dirtyLines.start = Math.min(this._dirtyLines.start, startLine);
+ this._dirtyLines.end = Math.max(this._dirtyLines.end, endLine);
+ } else {
+ this._dirtyLines = { start: startLine, end: endLine };
+ setTimeout(this._applyDomUpdates.bind(this), 0);
+ // Remove marked ranges, if any.
+ this.markAndRevealRange(null);
+ }
},
- /**
- * @param {Object} dirtyLines
- */
- _applyDomUpdates: function(dirtyLines)
+ _applyDomUpdates: function()
{
+ if (!this._dirtyLines)
+ return;
+
+ // Check if the editor had been set readOnly by the moment when this async callback got executed.
+ if (this._readOnly) {
+ delete this._dirtyLines;
+ return;
+ }
+
+ var dirtyLines = this._dirtyLines;
+
var firstChunkNumber = this._chunkNumberForLine(dirtyLines.start);
var startLine = this._textChunks[firstChunkNumber].startLine;
var endLine = this._textModel.linesCount;
@@ -2303,6 +2285,7 @@
}
var selection = this._getSelection();
+
if (lines.length === 0 && endLine < this._textModel.linesCount)
var oldRange = new WebInspector.TextRange(startLine, 0, endLine, 0);
else if (lines.length === 0 && startLine > 0)
@@ -2311,15 +2294,19 @@
var oldRange = new WebInspector.TextRange(startLine, startColumn, endLine - 1, endColumn);
var newContent = lines.join("\n");
- if (this._textModel.copyRange(oldRange) === newContent)
+ if (this._textModel.copyRange(oldRange) === newContent) {
+ delete this._dirtyLines;
return; // Noop
+ }
if (lines.length === 1 && lines[0] === "}" && oldRange.isEmpty() && selection.isEmpty() && !this._textModel.line(oldRange.endLine).trim())
this._unindentAfterBlock(oldRange, selection);
-
+
// This is a "foreign" call outside of this class. Should be before we delete the dirty lines flag.
this._enterTextChangeMode();
+ delete this._dirtyLines;
+
var newRange = this._editRange(oldRange, newContent);
this._paintScheduledLines(true);