Commit 9bee9f62 authored by luoe's avatar luoe Committed by Commit bot

DevTools: untruncate links on copy

During exporti or copying of selected text, links that were truncated will be
replaced with their original, un-trimmed text.

BUG=505177

Review-Url: https://codereview.chromium.org/2644753002
Cr-Commit-Position: refs/heads/master@{#468514}
parent 5a6b4429
......@@ -425,4 +425,31 @@ InspectorTest.waitForConsoleMessages = function(expectedCount, callback)
}
}
InspectorTest.selectConsoleMessages = function(fromMessage, fromTextOffset, toMessage, toTextOffset, useTextContainer)
{
var consoleView = Console.ConsoleView.instance();
var from = selectionContainerAndOffset(consoleView.itemElement(fromMessage).element(), fromTextOffset);
var to = selectionContainerAndOffset(consoleView.itemElement(toMessage).element(), toTextOffset);
window.getSelection().setBaseAndExtent(from.container, from.offset, to.container, to.offset);
function selectionContainerAndOffset(container, offset)
{
if (offset === 0 && container.nodeType !== Node.TEXT_NODE)
container = container.traverseNextTextNode();
var charCount = 0;
var node = container;
while (node = node.traverseNextTextNode(true)) {
var length = node.textContent.length;
if (charCount + length >= offset) {
return {
container: node,
offset: offset - charCount
};
}
charCount += length;
}
return null;
}
}
}
<html>
<head>
<script src="../../http/tests/inspector/inspector-test.js"></script>
<script src="../../http/tests/inspector/console-test.js"></script>
<script>
function test()
{
var longUrl = "www." + "z123456789".repeat(15) + ".com";
var shortUrl = "www.bar.com";
var mixedUrl = longUrl + " " + shortUrl + " " + longUrl;
var shortUrlWithHashes = "www." + "0123456789".repeat(2) + "zfoobarz" + "0123456789".repeat(2);
var urlWithHashes = "www." + "0123456789".repeat(2) + "z".repeat(150) + "0123456789".repeat(2);
var highlightedUrl = "www." + "z".repeat(200) + ".com";
var prepareCode = `
// Keep this as the first url logged to record the max truncated length.
console.log("${longUrl}");
console.log("${shortUrl}");
console.log("${longUrl}");
console.log("${mixedUrl}");
console.log("${shortUrlWithHashes}");
console.log("${urlWithHashes}");
console.log("${highlightedUrl}");
`;
var expectedMessageCount = 8;
var consoleView = Console.ConsoleView.instance();
var viewport = Console.ConsoleView.instance()._viewport;
var maxLength;
var halfMaxLength;
var secondLongUrlIndexInMixedUrl;
var tests = [
function testSelectWithinTruncatedUrl(next)
{
makeSelectionAndDump(1, 0, 1, halfMaxLength);
makeSelectionAndDump(1, 0, 1, halfMaxLength + 1);
makeSelectionAndDump(1, 0, 1, maxLength);
makeSelectionAndDump(1, halfMaxLength, 1, halfMaxLength + 1);
makeSelectionAndDump(1, halfMaxLength, 1, maxLength);
makeSelectionAndDump(1, halfMaxLength + 1, 1, maxLength);
next();
},
function testSelectAcrossMultipleMessages(next) {
makeSelectionAndDump(1, 0, 2, shortUrl.length);
makeSelectionAndDump(1, halfMaxLength, 2, shortUrl.length);
makeSelectionAndDump(1, halfMaxLength + 1, 2, shortUrl.length);
next()
},
function testSelectAcrossMultipleMessagesWithTruncatedUrls(next) {
makeSelectionAndDump(1, 0, 3, halfMaxLength);
makeSelectionAndDump(1, 0, 3, halfMaxLength + 1);
makeSelectionAndDump(1, 0, 3, maxLength);
next()
},
function testSelectWithinMessageWithMultipleTruncatedUrls(next) {
makeSelectionAndDump(4, 0, 4, halfMaxLength);
makeSelectionAndDump(4, 0, 4, halfMaxLength + 1);
makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl);
makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl + halfMaxLength);
makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1);
makeSelectionAndDump(4, 0, 4, secondLongUrlIndexInMixedUrl + maxLength);
makeSelectionAndDump(4, halfMaxLength, 4, halfMaxLength + 1);
makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl);
makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl + halfMaxLength);
makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1);
makeSelectionAndDump(4, halfMaxLength, 4, secondLongUrlIndexInMixedUrl + maxLength);
makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl);
makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + halfMaxLength);
makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1);
makeSelectionAndDump(4, halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + maxLength);
makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl, 4, secondLongUrlIndexInMixedUrl + halfMaxLength);
makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1);
makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl, 4, secondLongUrlIndexInMixedUrl + maxLength);
makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl + halfMaxLength, 4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1);
makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl + halfMaxLength, 4, secondLongUrlIndexInMixedUrl + maxLength);
makeSelectionAndDump(4, secondLongUrlIndexInMixedUrl + halfMaxLength + 1, 4, secondLongUrlIndexInMixedUrl + maxLength);
next()
},
function testSelectWithinShortUrlWithHashes(next)
{
var hashedUrlMaxLength = consoleMessageText(5).length;
var hashedUrlHalfMaxLength = Math.ceil(hashedUrlMaxLength / 2);
makeSelectionAndDump(5, 0, 5, hashedUrlHalfMaxLength);
makeSelectionAndDump(5, 0, 5, hashedUrlMaxLength);
makeSelectionAndDump(5, hashedUrlHalfMaxLength, 5, hashedUrlMaxLength);
next();
},
function testSelectWithinUrlWithHashes(next)
{
var hashedUrlMaxLength = consoleMessageText(6).length;
var hashedUrlHalfMaxLength = Math.ceil(hashedUrlMaxLength / 2);
makeSelectionAndDump(6, 0, 6, hashedUrlHalfMaxLength);
makeSelectionAndDump(6, 0, 6, hashedUrlHalfMaxLength + 1);
makeSelectionAndDump(6, 0, 6, hashedUrlMaxLength);
makeSelectionAndDump(6, hashedUrlHalfMaxLength, 6, hashedUrlHalfMaxLength + 1);
makeSelectionAndDump(6, hashedUrlHalfMaxLength, 6, hashedUrlMaxLength);
makeSelectionAndDump(6, hashedUrlHalfMaxLength + 1, 6, hashedUrlMaxLength);
next();
},
function testSelectWithinHighlightedUrlBeginning(next) {
testHighlightedUrlWithSearchQuery("www.", next);
},
function testSelectWithinHighlightedUrlMiddle(next) {
testHighlightedUrlWithSearchQuery("zzzzz", next);
},
function testSelectWithinHighlightedUrlEnd(next) {
testHighlightedUrlWithSearchQuery(".com", next);
}
];
InspectorTest.waitForConsoleMessages(expectedMessageCount, () => {
viewport.invalidate();
// Get the max truncated length from the first longUrl logged.
try {
var longUrlMessageText = consoleMessageText(1);
maxLength = longUrlMessageText.length;
halfMaxLength = Math.ceil(maxLength / 2);
secondLongUrlIndexInMixedUrl = maxLength + 1 + shortUrl.length + 1;
InspectorTest.addResult("Long url has max length: " + maxLength + ", text: " + longUrlMessageText);
} catch (e) {
InspectorTest.addResult("FAIL: Could not get max truncation length from first longUrl message.");
InspectorTest.completeTest();
return;
}
InspectorTest.runTestSuite(tests);
});
InspectorTest.evaluateInConsole(prepareCode);
function consoleMessageText(index) {
var messageElement = consoleView._visibleViewMessages[index].element();
return messageElement.querySelector('.console-message-text').deepTextContent();
}
function makeSelectionAndDump(fromMessage, fromTextOffset, toMessage, toTextOffset, includeAnchor, useTextContainer) {
InspectorTest.addResult("\nMaking selection: " + fromMessage + ", " + fromTextOffset + ", " + toMessage + ", " + toTextOffset);
// Ignore the anchor text on the start/end message, just use their contents.
if (!includeAnchor) {
var fromAnchor = consoleView.itemElement(fromMessage).element().querySelector('.console-message-anchor');
var toAnchor = consoleView.itemElement(toMessage).element().querySelector('.console-message-anchor');
fromTextOffset += fromAnchor ? fromAnchor.deepTextContent().length : 0;
toTextOffset += toAnchor ? toAnchor.deepTextContent().length : 0;
}
InspectorTest.selectConsoleMessages(fromMessage, fromTextOffset, toMessage, toTextOffset, useTextContainer);
var selectedText = viewport._selectedText();
if (selectedText)
InspectorTest.addResult("Selection length: " + selectedText.length + ", " + "text: " + selectedText.replace(/\bVM\d+/g, "VM"));
else
InspectorTest.addResult("No selection");
}
function testHighlightedUrlWithSearchQuery(query, next) {
// Clear any existing ranges to avoid using them as the query.
window.getSelection().removeAllRanges();
InspectorTest.addSniffer(consoleView, "_searchFinishedForTests", onSearch);
consoleView._searchableView._searchInputElement.value = query;
consoleView._searchableView._regexButton.setToggled(false);
consoleView._searchableView._caseSensitiveButton.setToggled(false);
consoleView._searchableView.showSearchField();
InspectorTest.addResult("Searching for text: " + query);
function onSearch() {
var matches = consoleView.element.childTextNodes().filter(node => node.parentElement.classList.contains("highlighted-search-result")).map(node => node.parentElement);
InspectorTest.addResult("Highlighted " + matches.length + " matches");
// Use TextNodes for containers to get inside the highlighted match element.
makeSelectionAndDump(7, 0, 7, halfMaxLength, false, true);
makeSelectionAndDump(7, 0, 7, halfMaxLength + 1, false, true);
makeSelectionAndDump(7, 0, 7, maxLength, false, true);
makeSelectionAndDump(7, halfMaxLength, 7, halfMaxLength + 1);
makeSelectionAndDump(7, halfMaxLength, 7, maxLength);
makeSelectionAndDump(7, halfMaxLength + 1, 7, maxLength);
next();
}
}
}
</script>
</head>
<body onload="runTest()">
<p>Tests that console copies tree outline messages properly.</p>
</body>
</html>
......@@ -259,23 +259,6 @@ function test()
viewport.refresh();
}
function selectionContainerAndOffset(container, offset)
{
var charCount = 0;
var node = container;
while (node = node.traverseNextTextNode(true)) {
var length = node.textContent.length;
if (charCount + length >= offset) {
return {
container: node,
offset: offset - charCount
};
}
charCount += length;
}
return null;
}
function selectMessages(fromMessage, fromTextOffset, toMessage, toTextOffset)
{
if (Math.abs(toMessage - fromMessage) > minimumViewportMessagesCount) {
......@@ -285,9 +268,7 @@ function test()
}
viewport.forceScrollItemToBeFirst(Math.min(fromMessage, toMessage));
var from = selectionContainerAndOffset(consoleView.itemElement(fromMessage).element(), fromTextOffset);
var to = selectionContainerAndOffset(consoleView.itemElement(toMessage).element(), toTextOffset);
window.getSelection().setBaseAndExtent(from.container, from.offset, to.container, to.offset);
InspectorTest.selectConsoleMessages(fromMessage, fromTextOffset, toMessage, toTextOffset);
viewport.refresh();
}
}
......
......@@ -303,13 +303,8 @@ Components.Linkifier = class {
return;
Components.Linkifier._bindUILocation(anchor, uiLocation);
var text = uiLocation.linkText();
var info = Components.Linkifier._linkInfo(anchor);
info.originalLinkText = text;
text = text.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, '$1\u2026');
if (this._maxLength)
text = text.trimMiddle(this._maxLength);
anchor.textContent = text;
var text = uiLocation.linkText(true /* skipTrim */);
Components.Linkifier._setTrimmedText(anchor, text, this._maxLength);
var titleText = uiLocation.uiSourceCode.url();
if (typeof uiLocation.lineNumber === 'number')
......@@ -396,9 +391,7 @@ Components.Linkifier = class {
link.title = title;
if (href)
link.href = href;
link.textContent = text;
if (maxLength)
link.textContent = link.textContent.trimMiddle(maxLength);
Components.Linkifier._setTrimmedText(link, text, maxLength);
link[Components.Linkifier._infoSymbol] = {
icon: null,
enableDecorator: false,
......@@ -408,8 +401,7 @@ Components.Linkifier = class {
lineNumber: null,
columnNumber: null,
revealable: null,
fallback: null,
originalLinkText: text
fallback: null
};
if (!preventClick)
link.addEventListener('click', Components.Linkifier._handleClick, false);
......@@ -419,12 +411,70 @@ Components.Linkifier = class {
}
/**
* @param {?Element} link
* @return {?string}
* @param {!Element} link
* @param {string} text
* @param {number=} maxLength
*/
static _setTrimmedText(link, text, maxLength) {
link.removeChildren();
if (maxLength && text.length > maxLength) {
var middleSplit = splitMiddle(text, maxLength);
appendTextWithoutHashes(middleSplit[0]);
appendHiddenText(middleSplit[1]);
appendTextWithoutHashes(middleSplit[2]);
} else {
appendTextWithoutHashes(text);
}
/**
* @param {string} string
*/
function appendHiddenText(string) {
var ellipsisNode = link.createChild('span', 'devtools-link-ellipsis').createTextChild('\u2026');
ellipsisNode[Components.Linkifier._untruncatedNodeTextSymbol] = string;
}
/**
* @param {string} string
*/
function appendTextWithoutHashes(string) {
var hashSplit = TextUtils.TextUtils.splitStringByRegexes(string, [/[a-f0-9]{20,}/g]);
for (var match of hashSplit) {
if (match.regexIndex === -1) {
link.createTextChild(match.value);
} else {
link.createTextChild(match.value.substring(0, 7));
appendHiddenText(match.value.substring(7));
}
}
}
/**
* @param {string} string
* @param {number} maxLength
* @return {!Array<string>}
*/
function splitMiddle(string, maxLength) {
var leftIndex = Math.floor(maxLength / 2);
var rightIndex = string.length - Math.ceil(maxLength / 2) + 1;
// Do not truncate between characters that use multiple code points (emojis).
if (string.codePointAt(rightIndex - 1) >= 0x10000) {
rightIndex++;
leftIndex++;
}
if (leftIndex > 0 && string.codePointAt(leftIndex - 1) >= 0x10000)
leftIndex--;
return [string.substring(0, leftIndex), string.substring(leftIndex, rightIndex), string.substring(rightIndex)];
}
}
/**
* @param {!Node} node
* @return {string}
*/
static originalLinkText(link) {
var info = this._linkInfo(link);
return info ? info.originalLinkText : null;
static untruncatedNodeText(node) {
return node[Components.Linkifier._untruncatedNodeTextSymbol] || node.textContent;
}
/**
......@@ -549,6 +599,7 @@ Components.Linkifier._decorator = null;
Components.Linkifier._sourceCodeAnchors = Symbol('Linkifier.anchors');
Components.Linkifier._infoSymbol = Symbol('Linkifier.info');
Components.Linkifier._untruncatedNodeTextSymbol = Symbol('Linkifier.untruncatedNodeText');
/**
* @typedef {{
......@@ -560,8 +611,7 @@ Components.Linkifier._infoSymbol = Symbol('Linkifier.info');
* lineNumber: ?number,
* columnNumber: ?number,
* revealable: ?Object,
* fallback: ?Element,
* originalLinkText: string
* fallback: ?Element
* }}
*/
Components._LinkInfo;
......
......@@ -1040,11 +1040,7 @@ Console.ConsoleViewMessage = class {
toExportString() {
var lines = [];
var nodes = this.contentElement().childTextNodes();
var messageContent = '';
for (var i = 0; i < nodes.length; ++i) {
var originalLinkText = Components.Linkifier.originalLinkText(nodes[i].parentElement);
messageContent += typeof originalLinkText === 'string' ? originalLinkText : nodes[i].textContent;
}
var messageContent = nodes.map(Components.Linkifier.untruncatedNodeText).join('');
for (var i = 0; i < this.repeatCount(); ++i)
lines.push(messageContent);
return lines.join('\n');
......
......@@ -420,8 +420,11 @@ Console.ConsoleViewport = class {
}
var textLines = [];
for (var i = startSelection.item; i <= endSelection.item; ++i)
textLines.push(this._providerElement(i).element().deepTextContent());
for (var i = startSelection.item; i <= endSelection.item; ++i) {
var element = this._providerElement(i).element();
var lineContent = element.childTextNodes().map(Components.Linkifier.untruncatedNodeText).join('');
textLines.push(lineContent);
}
var endSelectionElement = this._providerElement(endSelection.item).element();
if (endSelection.node && endSelection.node.isSelfOrDescendant(endSelectionElement)) {
......@@ -453,10 +456,15 @@ Console.ConsoleViewport = class {
offset = container.textContent.length;
}
}
var chars = 0;
var node = itemElement;
while ((node = node.traverseNextTextNode(itemElement)) && !node.isSelfOrDescendant(container))
chars += node.textContent.length;
chars += Components.Linkifier.untruncatedNodeText(node).length;
// If the selection offset is at the end of a link's ellipsis, use the untruncated length as offset.
var untruncatedContainerLength = Components.Linkifier.untruncatedNodeText(container).length;
if (offset === 1 && untruncatedContainerLength > offset)
offset = untruncatedContainerLength;
return chars + offset;
}
......
......@@ -367,7 +367,7 @@ Sources.CallStackSidebarPane = class extends UI.SimpleView {
var location = this._itemLocation(item);
if (location) {
var uiLocation = Bindings.debuggerWorkspaceBinding.rawLocationToUILocation(location);
itemText += ' (' + uiLocation.linkText() + ')';
itemText += ' (' + uiLocation.linkText(true /* skipTrim */) + ')';
}
text.push(itemText);
}
......
......@@ -667,10 +667,11 @@ Workspace.UILocation = class {
}
/**
* @param {boolean=} skipTrim
* @return {string}
*/
linkText() {
var linkText = this.uiSourceCode.displayName();
linkText(skipTrim) {
var linkText = this.uiSourceCode.displayName(skipTrim);
if (typeof this.lineNumber === 'number')
linkText += ':' + (this.lineNumber + 1);
return linkText;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment