diff options
Diffstat (limited to 'devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js')
-rw-r--r-- | devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js b/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js new file mode 100644 index 0000000000..991027c67b --- /dev/null +++ b/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js @@ -0,0 +1,209 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const { + FILTER_FLAGS, + SUPPORTED_HTTP_CODES, +} = require("resource://devtools/client/netmonitor/src/constants.js"); + +const { + getRequestPriorityAsText, +} = require("resource://devtools/client/netmonitor/src/utils/format-utils.js"); + +/** + * Generates a value for the given filter + * ie. if flag = status-code, will generate "200" from the given request item. + * For flags related to cookies, it might generate an array based on the request + * ie. ["cookie-name-1", "cookie-name-2", ...] + * + * @param {string} flag - flag specified in filter, ie. "status-code" + * @param {object} request - Network request item + * @return {string|Array} - The output is a string or an array based on the request + */ +function getAutocompleteValuesForFlag(flag, request) { + let values = []; + let { responseCookies = { cookies: [] } } = request; + responseCookies = responseCookies.cookies || responseCookies; + + switch (flag) { + case "status-code": + // Sometimes status comes as Number + values.push(String(request.status)); + break; + case "scheme": + values.push(request.urlDetails.scheme); + break; + case "domain": + values.push(request.urlDetails.host); + break; + case "remote-ip": + values.push(request.remoteAddress); + break; + case "cause": + values.push(request.cause.type); + break; + case "mime-type": + values.push((request.mimeType || "").replace(/;.+/, "")); + break; + case "set-cookie-name": + values = responseCookies.map(c => c.name); + break; + case "set-cookie-value": + values = responseCookies.map(c => c.value); + break; + case "priority": + values.push(getRequestPriorityAsText(request.priority)); + break; + case "set-cookie-domain": + values = responseCookies.map(c => + c.hasOwnProperty("domain") ? c.domain : request.urlDetails.host + ); + break; + case "is": + values = ["cached", "from-cache", "running"]; + break; + case "has-response-header": + // Some requests not having responseHeaders..? + values = request.responseHeaders + ? request.responseHeaders.headers.map(h => h.name) + : []; + break; + case "protocol": + values.push(request.httpVersion); + break; + case "method": + default: + values.push(request[flag]); + } + + return values; +} + +/** + * For a given lastToken passed ie. "is:", returns an array of populated flag + * values for consumption in autocompleteProvider + * ie. ["is:cached", "is:running", "is:from-cache"] + * + * @param {string} lastToken - lastToken parsed from filter input, ie "is:" + * @param {object} requests - List of requests from which values are generated + * @return {Array} - array of autocomplete values + */ +function getLastTokenFlagValues(lastToken, requests) { + // The last token must be a string like "method:GET" or "method:", Any token + // without a ":" cant be used to parse out flag values + if (!lastToken.includes(":")) { + return []; + } + + // Parse out possible flag from lastToken + let [flag, typedFlagValue] = lastToken.split(":"); + let isNegativeFlag = false; + + // Check if flag is used with negative match + if (flag.startsWith("-")) { + flag = flag.slice(1); + isNegativeFlag = true; + } + + // Flag is some random string, return + if (!FILTER_FLAGS.includes(flag)) { + return []; + } + + let values = []; + for (const request of requests) { + values.push(...getAutocompleteValuesForFlag(flag, request)); + } + values = [...new Set(values)]; + + return values + .filter(value => value) + .filter(value => { + if (typedFlagValue && value) { + const lowerTyped = typedFlagValue.toLowerCase(); + const lowerValue = value.toLowerCase(); + return lowerValue.includes(lowerTyped) && lowerValue !== lowerTyped; + } + return ( + typeof value !== "undefined" && value !== "" && value !== "undefined" + ); + }) + .sort() + .map(value => (isNegativeFlag ? `-${flag}:${value}` : `${flag}:${value}`)); +} + +/** + * Generates an autocomplete list for the search-box for network monitor + * + * It expects an entire string of the searchbox ie "is:cached pr". + * The string is then tokenized into "is:cached" and "pr" + * + * @param {string} filter - The entire search string of the search box + * @param {object} requests - Iteratable object of requests displayed + * @return {Array} - The output is an array of objects as below + * [{value: "is:cached protocol", displayValue: "protocol"}[, ...]] + * `value` is used to update the search-box input box for given item + * `displayValue` is used to render the autocomplete list + */ +function autocompleteProvider(filter, requests) { + if (!filter) { + return []; + } + + const negativeAutocompleteList = FILTER_FLAGS.map(item => `-${item}`); + const baseList = [...FILTER_FLAGS, ...negativeAutocompleteList].map( + item => `${item}:` + ); + + // The last token is used to filter the base autocomplete list + const tokens = filter.split(/\s+/g); + const lastToken = tokens[tokens.length - 1]; + const previousTokens = tokens.slice(0, tokens.length - 1); + + // Autocomplete list is not generated for empty lastToken + if (!lastToken) { + return []; + } + + let autocompleteList; + const availableValues = getLastTokenFlagValues(lastToken, requests); + if (availableValues.length) { + autocompleteList = availableValues; + } else { + const isNegativeFlag = lastToken.startsWith("-"); + + // Stores list of HTTP codes that starts with value of lastToken + const filteredStatusCodes = SUPPORTED_HTTP_CODES.filter(item => { + item = isNegativeFlag ? item.substr(1) : item; + return item.toLowerCase().startsWith(lastToken.toLowerCase()); + }); + + if (filteredStatusCodes.length) { + // Shows an autocomplete list of "status-code" values from filteredStatusCodes + autocompleteList = isNegativeFlag + ? filteredStatusCodes.map(item => `-status-code:${item}`) + : filteredStatusCodes.map(item => `status-code:${item}`); + } else { + // Shows an autocomplete list of values from baseList + // that starts with value of lastToken + autocompleteList = baseList.filter(item => { + return ( + item.toLowerCase().startsWith(lastToken.toLowerCase()) && + item.toLowerCase() !== lastToken.toLowerCase() + ); + }); + } + } + + return autocompleteList.sort().map(item => ({ + value: [...previousTokens, item].join(" "), + displayValue: item, + })); +} + +module.exports = { + autocompleteProvider, +}; |