Bug 1777608 - [devtools] Change how we render cropped URLs in String rep. r=bomsy.

Instead of rendering the cropped URL, we split the URL in 3 parts, so the full
URL text will be in the DOM, but we visually hide the middle part and replace
it with an ellipsis.
This way copying the message will still put the full URL in the clipboard.
A test case is added to ensure this works as expected.

Differential Revision: https://phabricator.services.mozilla.com/D165805
This commit is contained in:
Nicolas Chevobbe 2023-02-13 16:01:21 +00:00
parent 2c8367836d
commit 15e9036e65
6 changed files with 88 additions and 32 deletions

View file

@ -59,6 +59,10 @@
font-style: italic;
}
.objectBox-string a {
word-break: break-all;
}
.objectBox-string a,
.objectBox-string a:visited {
color: currentColor;
@ -71,6 +75,20 @@
text-decoration: underline;
}
/* Visually hide the middle of "cropped" url */
.objectBox-string a .cropped-url-middle {
max-width: 0;
max-height: 0;
display: inline-block;
overflow: hidden;
vertical-align: bottom;
}
.objectBox-string a .cropped-url-end::before {
content: "…";
}
.objectBox-function,
.objectBox-profile {
color: var(--object-color);

View file

@ -252,7 +252,7 @@ define(function(require, exports, module) {
}
currentIndex = currentIndex + contentStart;
let linkText = getCroppedString(
const linkText = getCroppedString(
useUrl,
currentIndex,
startCropIndex,
@ -260,21 +260,35 @@ define(function(require, exports, module) {
);
if (linkText) {
if (urlCropLimit && useUrl.length > urlCropLimit) {
const linkItems = [];
const shouldCrop = urlCropLimit && useUrl.length > urlCropLimit;
if (shouldCrop) {
const urlCropHalf = Math.ceil((urlCropLimit - ELLIPSIS.length) / 2);
linkText = getCroppedString(
useUrl,
0,
urlCropHalf,
useUrl.length - urlCropHalf
// We cut the string into 3 elements and we'll visually hide the second one
// in CSS. This way people can still copy the full link.
linkItems.push(
span(
{ className: "cropped-url-start" },
useUrl.substring(0, urlCropHalf)
),
span(
{ className: "cropped-url-middle" },
useUrl.substring(urlCropHalf, useUrl.length - urlCropHalf)
),
span(
{ className: "cropped-url-end" },
useUrl.substring(useUrl.length - urlCropHalf)
)
);
} else {
linkItems.push(linkText);
}
items.push(
a(
{
key: `${useUrl}-${currentIndex}`,
className: "url",
className: "url" + (shouldCrop ? " cropped-url" : ""),
title: useUrl,
draggable: false,
// Because we don't want the link to be open in the current
@ -291,7 +305,7 @@ define(function(require, exports, module) {
}
: null,
},
linkText
linkItems
)
);
}

View file

@ -440,10 +440,16 @@ describe("test String with URL", () => {
urlCropLimit: 20,
});
expect(element.text()).toEqual("http://xyz…klmnopqrst is the best");
const link = element.find("a").at(0);
expect(element.text()).toEqual(text);
const link = element.find("a.cropped-url").at(0);
expect(link.prop("href")).toBe(xyzUrl);
expect(link.prop("title")).toBe(xyzUrl);
const linkParts = link.find("span");
expect(linkParts.at(0).hasClass("cropped-url-start")).toBe(true);
expect(linkParts.at(0).text()).toEqual("http://xyz");
expect(linkParts.at(1).hasClass("cropped-url-middle")).toBe(true);
expect(linkParts.at(2).hasClass("cropped-url-end")).toBe(true);
expect(linkParts.at(2).text()).toEqual("klmnopqrst");
});
it("renders multiple cropped URL", () => {
@ -457,17 +463,28 @@ describe("test String with URL", () => {
urlCropLimit: 20,
});
expect(element.text()).toEqual(
"http://xyz…klmnopqrst is lit, not http://abc…klmnopqrst"
);
expect(element.text()).toEqual(`${xyzUrl} is lit, not ${abcUrl}`);
const links = element.find("a");
const links = element.find("a.cropped-url");
const xyzLink = links.at(0);
expect(xyzLink.prop("href")).toBe(xyzUrl);
expect(xyzLink.prop("title")).toBe(xyzUrl);
const xyzLinkParts = xyzLink.find("span");
expect(xyzLinkParts.at(0).hasClass("cropped-url-start")).toBe(true);
expect(xyzLinkParts.at(0).text()).toEqual("http://xyz");
expect(xyzLinkParts.at(1).hasClass("cropped-url-middle")).toBe(true);
expect(xyzLinkParts.at(2).hasClass("cropped-url-end")).toBe(true);
expect(xyzLinkParts.at(2).text()).toEqual("klmnopqrst");
const abc = links.at(1);
expect(abc.prop("href")).toBe(abcUrl);
expect(abc.prop("title")).toBe(abcUrl);
const abcLinkParts = abc.find("span");
expect(abcLinkParts.at(0).hasClass("cropped-url-start")).toBe(true);
expect(abcLinkParts.at(0).text()).toEqual("http://abc");
expect(abcLinkParts.at(1).hasClass("cropped-url-middle")).toBe(true);
expect(abcLinkParts.at(2).hasClass("cropped-url-end")).toBe(true);
expect(abcLinkParts.at(2).text()).toEqual("klmnopqrst");
});
it("renders full URL if smaller than cropLimit", () => {
@ -484,6 +501,7 @@ describe("test String with URL", () => {
const link = element.find("a").at(0);
expect(link.prop("href")).toBe(xyzUrl);
expect(link.prop("title")).toBe(xyzUrl);
expect(link.find(".cropped-url-start").length).toBe(0);
});
it("renders cropped URL followed by cropped string with urlCropLimit", () => {

View file

@ -21,6 +21,7 @@ httpServer.registerPathHandler("/test.js", function(request, response) {
console.log(new Error("error object"));
console.trace();
for (let i = 0; i < 2; i++) console.log("repeated")
console.log(document.location + "?" + "z".repeat(100))
}
wrapper();
};
@ -122,7 +123,7 @@ async function testMessagesCopy(hud, timestamp) {
);
is(
lines[2],
` logStuff ${TEST_URI}test.js:9`,
` logStuff ${TEST_URI}test.js:10`,
"Stacktrace second line has the expected text"
);
@ -151,7 +152,7 @@ async function testMessagesCopy(hud, timestamp) {
);
is(
lines[2],
` logStuff ${TEST_URI}test.js:9`,
` logStuff ${TEST_URI}test.js:10`,
"Error Stacktrace second line has the expected text"
);
@ -174,7 +175,7 @@ async function testMessagesCopy(hud, timestamp) {
}
is(
lines[1],
` <anonymous> ${TEST_URI}test.js:11`,
` <anonymous> ${TEST_URI}test.js:12`,
"ReferenceError second line has expected text"
);
ok(
@ -190,6 +191,15 @@ async function testMessagesCopy(hud, timestamp) {
message = await waitFor(() => findConsoleAPIMessage(hud, "repeated 2"));
clipboardText = await copyMessageContent(hud, message);
ok(true, "Clipboard text was found and saved");
info("Test copy menu item for the message with the cropped URL");
message = await waitFor(() => findConsoleAPIMessage(hud, "z".repeat(100)));
ok(!!message.querySelector("a.cropped-url"), "URL is cropped");
clipboardText = await copyMessageContent(hud, message);
ok(
clipboardText.startsWith(TEST_URI) + "?" + "z".repeat(100),
"Full URL was copied to clipboard"
);
}
function getTimestampText(messageEl) {

View file

@ -33,6 +33,11 @@ add_task(async function() {
return `${origin}${params}`;
};
const getVisibleLinkText = linkEl => {
const [firstPart, , lastPart] = linkEl.children;
return `${firstPart.innerText}${ELLIPSIS}${lastPart.innerText}`;
};
const EXPECTED_MESSAGE = `get more information on this error`;
const msg = await waitFor(() => findErrorMessage(hud, EXPECTED_MESSAGE));
@ -42,7 +47,7 @@ add_task(async function() {
is(comLink.getAttribute("href"), url1, "First link has expected url");
is(comLink.getAttribute("title"), url1, "First link has expected tooltip");
is(
comLink.textContent,
getVisibleLinkText(comLink),
getCroppedUrl("https://example.com"),
"First link has expected text"
);
@ -50,7 +55,7 @@ add_task(async function() {
is(orgLink.getAttribute("href"), url2, "Second link has expected url");
is(orgLink.getAttribute("title"), url2, "Second link has expected tooltip");
is(
orgLink.textContent,
getVisibleLinkText(orgLink),
getCroppedUrl("https://example.org"),
"Second link has expected text"
);

View file

@ -405,22 +405,13 @@ describe("PageError component:", () => {
const message = prepareMessage(packet, { getNextId: () => "1" });
const wrapper = render(PageError({ message, serviceContainer }));
// Keep in sync with `urlCropLimit` in PageError.js.
const cropLimit = 120;
const partLength = cropLimit / 2;
const getCroppedUrl = url =>
`${url}${"a".repeat(partLength - url.length)}${"a".repeat(partLength)}`;
const croppedEvil = getCroppedUrl(evilDomain);
const croppedbad = getCroppedUrl(badDomain);
const text = wrapper.find(".message-body").text();
expect(text).toBe(
`Uncaught “${croppedEvil}“ is evil and “${croppedbad}“ is not good either`
`Uncaught “${evilURL}“ is evil and “${badURL}“ is not good either`
);
// There should be 2 links.
const links = wrapper.find(".message-body a");
// There should be 2 cropped links.
const links = wrapper.find(".message-body a.cropped-url");
expect(links.length).toBe(2);
expect(links.eq(0).attr("href")).toBe(evilURL);