Bug 1441079 - Allow to filter console by regular expression. r=nchevobbe

Any text enclosed between forward slashes is considered as a regex search. If the entered regex is invalid, a normal text search is performed.

Differential Revision: https://phabricator.services.mozilla.com/D26310

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Hemakshi Sachdev 2019-04-10 05:28:38 +00:00
parent 315a07dce8
commit 4c96eaa08d
4 changed files with 153 additions and 35 deletions

View File

@ -959,7 +959,15 @@ function passCssFilters(message, filters) {
* @returns {Boolean}
*/
function passSearchFilters(message, filters) {
const text = (filters.text || "").trim();
const text = (filters.text || "").trim().toLocaleLowerCase();
let regex;
if (text.startsWith("/") && text.endsWith("/") && text.length > 2) {
try {
regex = new RegExp(text.slice(1, -1), "im");
} catch (e) {
}
}
// If there is no search, the message passes the filter.
if (!text) {
@ -968,26 +976,26 @@ function passSearchFilters(message, filters) {
return (
// Look for a match in parameters.
isTextInParameters(text, message.parameters)
isTextInParameters(text, regex, message.parameters)
// Look for a match in location.
|| isTextInFrame(text, message.frame)
|| isTextInFrame(text, regex, message.frame)
// Look for a match in net events.
|| isTextInNetEvent(text, message.request)
|| isTextInNetEvent(text, regex, message.request)
// Look for a match in stack-trace.
|| isTextInStackTrace(text, message.stacktrace)
|| isTextInStackTrace(text, regex, message.stacktrace)
// Look for a match in messageText.
|| isTextInMessageText(text, message.messageText)
|| isTextInMessageText(text, regex, message.messageText)
// Look for a match in notes.
|| isTextInNotes(text, message.notes)
|| isTextInNotes(text, regex, message.notes)
// Look for a match in prefix.
|| isTextInPrefix(text, message.prefix)
|| isTextInPrefix(text, regex, message.prefix)
);
}
/**
* Returns true if given text is included in provided stack frame.
*/
function isTextInFrame(text, frame) {
function isTextInFrame(text, regex, frame) {
if (!frame) {
return false;
}
@ -1001,53 +1009,50 @@ function isTextInFrame(text, frame) {
const { short } = getSourceNames(source);
const unicodeShort = getUnicodeUrlPath(short);
const includes =
`${functionName ? functionName + " " : ""}${unicodeShort}:${line}:${column}`
.toLocaleLowerCase()
.includes(text.toLocaleLowerCase());
return includes;
const str =
`${functionName ? functionName + " " : ""}${unicodeShort}:${line}:${column}`;
return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text);
}
/**
* Returns true if given text is included in provided parameters.
*/
function isTextInParameters(text, parameters) {
function isTextInParameters(text, regex, parameters) {
if (!parameters) {
return false;
}
text = text.toLocaleLowerCase();
return getAllProps(parameters).some(prop =>
(prop + "").toLocaleLowerCase().includes(text)
);
return getAllProps(parameters).some(prop => {
const str = (prop + "");
return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text);
});
}
/**
* Returns true if given text is included in provided net event grip.
*/
function isTextInNetEvent(text, request) {
function isTextInNetEvent(text, regex, request) {
if (!request) {
return false;
}
text = text.toLocaleLowerCase();
const method = request.method.toLocaleLowerCase();
const url = request.url.toLocaleLowerCase();
return method.includes(text) || url.includes(text);
const method = request.method;
const url = request.url;
return regex ? regex.test(method) || regex.test(url) :
method.toLocaleLowerCase().includes(text) || url.toLocaleLowerCase().includes(text);
}
/**
* Returns true if given text is included in provided stack trace.
*/
function isTextInStackTrace(text, stacktrace) {
function isTextInStackTrace(text, regex, stacktrace) {
if (!Array.isArray(stacktrace)) {
return false;
}
// isTextInFrame expect the properties of the frame object to be in the same
// order they are rendered in the Frame component.
return stacktrace.some(frame => isTextInFrame(text, {
return stacktrace.some(frame => isTextInFrame(text, regex, {
functionName: frame.functionName || l10n.getStr("stacktrace.anonymousFunction"),
source: frame.filename,
lineNumber: frame.lineNumber,
@ -1058,17 +1063,19 @@ function isTextInStackTrace(text, stacktrace) {
/**
* Returns true if given text is included in `messageText` field.
*/
function isTextInMessageText(text, messageText) {
function isTextInMessageText(text, regex, messageText) {
if (!messageText) {
return false;
}
if (typeof messageText === "string") {
return messageText.toLocaleLowerCase().includes(text.toLocaleLowerCase());
return regex ? regex.test(messageText) :
messageText.toLocaleLowerCase().includes(text);
}
if (messageText.type === "longString") {
return messageText.initial.toLocaleLowerCase().includes(text.toLocaleLowerCase());
return regex ? regex.test(messageText.initial) :
messageText.initial.toLocaleLowerCase().includes(text);
}
return true;
@ -1077,18 +1084,21 @@ function isTextInMessageText(text, messageText) {
/**
* Returns true if given text is included in notes.
*/
function isTextInNotes(text, notes) {
function isTextInNotes(text, regex, notes) {
if (!Array.isArray(notes)) {
return false;
}
return notes.some(note =>
// Look for a match in location.
isTextInFrame(text, note.frame) ||
isTextInFrame(text, regex, note.frame) ||
// Look for a match in messageBody.
(
note.messageBody &&
note.messageBody.toLocaleLowerCase().includes(text.toLocaleLowerCase())
(
regex ? regex.test(note.messageBody) :
note.messageBody.toLocaleLowerCase().includes(text)
)
)
);
}
@ -1096,12 +1106,14 @@ function isTextInNotes(text, notes) {
/**
* Returns true if given text is included in prefix.
*/
function isTextInPrefix(text, prefix) {
function isTextInPrefix(text, regex, prefix) {
if (!prefix) {
return false;
}
return `${prefix}: `.toLocaleLowerCase().includes(text.toLocaleLowerCase());
const str = `${prefix}: `;
return regex ? regex.test(str) : str.toLocaleLowerCase().includes(text);
}
/**

View File

@ -32,6 +32,7 @@ support-files =
test-click-function-to-source.js
test-closure-optimized-out.html
test-console-filters.html
test-console-filter-by-regex-input.html
test-console-group.html
test-console-iframes.html
test-console-table.html
@ -297,6 +298,7 @@ subsuite = clipboard
[browser_webconsole_file_uri.js]
skip-if = true # Bug 1404382
[browser_webconsole_filter_by_input.js]
[browser_webconsole_filter_by_regex_input.js]
[browser_webconsole_filter_scroll.js]
[browser_webconsole_filters.js]
[browser_webconsole_filters_persist.js]

View File

@ -0,0 +1,84 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const MESSAGES = [
"123-456-7890",
"foo@bar.com",
"http://abc.com/q?fizz=buzz&alpha=beta/",
"https://xyz.com/?path=/world",
"foooobaaaar",
"123 working",
];
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
"test/mochitest/test-console-filter-by-regex-input.html";
add_task(async function() {
const hud = await openNewTabAndConsole(TEST_URI);
const { outputNode } = hud.ui;
await waitFor(() => findMessage(hud, MESSAGES[5]), null, 200);
let filteredNodes;
info("Filter out messages that begin with numbers");
await setFilterInput(hud, "/^[0-9]/", MESSAGES[5]);
filteredNodes = outputNode.querySelectorAll(".message");
checkFilteredMessages(filteredNodes, [
MESSAGES[0],
MESSAGES[5],
], 2);
info("Filter out messages that are phone numbers");
await setFilterInput(hud, "/\\d{3}\\-\\d{3}\\-\\d{4}/", MESSAGES[0]);
filteredNodes = outputNode.querySelectorAll(".message");
checkFilteredMessages(filteredNodes, [MESSAGES[0]], 1);
info("Filter out messages that are an email address");
await setFilterInput(hud, "/^\\w+@[a-zA-Z]+\\.[a-zA-Z]{2,3}$/", MESSAGES[1]);
filteredNodes = outputNode.querySelectorAll(".message");
checkFilteredMessages(filteredNodes, [MESSAGES[1]], 1);
info("Filter out messages that contain query strings");
await setFilterInput(hud, "/\\?([^=&]+=[^=&]+&?)*\\//", MESSAGES[2]);
filteredNodes = outputNode.querySelectorAll(".message");
checkFilteredMessages(filteredNodes, [MESSAGES[2]], 1);
// If regex is invalid, do a normal text search instead
info("Filter messages using normal text search if regex is invalid");
await setFilterInput(hud, "/?path=/", MESSAGES[3]);
filteredNodes = outputNode.querySelectorAll(".message");
checkFilteredMessages(filteredNodes, [MESSAGES[3]], 1);
info("Filter out messages not ending with numbers");
await setFilterInput(hud, "/[^0-9]$/", MESSAGES[5]);
filteredNodes = outputNode.querySelectorAll(".message");
checkFilteredMessages(filteredNodes, [
MESSAGES[1],
MESSAGES[2],
MESSAGES[3],
MESSAGES[4],
MESSAGES[5],
], 5);
});
async function setFilterInput(hud, value, lastMessage) {
hud.ui.filterBox.focus();
hud.ui.filterBox.select();
EventUtils.sendString(value);
await waitFor(() => findMessage(hud, lastMessage), null, 200);
}
function checkFilteredMessages(filteredNodes, expectedMessages, expectedCount) {
is(filteredNodes.length, expectedCount,
`${expectedCount} messages should be displayed`);
filteredNodes.forEach((node, id) => {
const messageBody = node.querySelector(".message-body").textContent;
ok(messageBody, expectedMessages[id]);
});
}

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Web Console test regex input.</title>
</head>
<body>
<p>Web Console test for filtering messages by regex input.</p>
<script>
"use strict";
console.log("123-456-7890");
console.log("foo@bar.com");
console.log("http://abc.com/q?fizz=buzz&alpha=beta/");
console.log("https://xyz.com/?path=/world");
console.log("foooobaaaar");
console.log("123 working");
</script>
</body>
</html>