diff options
Diffstat (limited to 'devtools/client/netmonitor/src/workers')
5 files changed, 409 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/workers/moz.build b/devtools/client/netmonitor/src/workers/moz.build new file mode 100644 index 0000000000..a1384c1e10 --- /dev/null +++ b/devtools/client/netmonitor/src/workers/moz.build @@ -0,0 +1,8 @@ +# vim: set filetype=python: +# 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/. + +DIRS += [ + "search", +] diff --git a/devtools/client/netmonitor/src/workers/search/index.js b/devtools/client/netmonitor/src/workers/search/index.js new file mode 100644 index 0000000000..a721454034 --- /dev/null +++ b/devtools/client/netmonitor/src/workers/search/index.js @@ -0,0 +1,25 @@ +/* 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 { + WorkerDispatcher, +} = require("resource://devtools/client/shared/worker-utils.js"); + +const SEARCH_WORKER_URL = + "resource://devtools/client/netmonitor/src/workers/search/worker.js"; + +class SearchDispatcher extends WorkerDispatcher { + constructor() { + super(SEARCH_WORKER_URL); + } + + // The search worker support just one task at this point, + // which is searching through specified resource. + searchInResource = this.task("searchInResource"); +} + +// Compared to debugger, we instantiate a singleton within the dispatcher module +module.exports = new SearchDispatcher(); diff --git a/devtools/client/netmonitor/src/workers/search/moz.build b/devtools/client/netmonitor/src/workers/search/moz.build new file mode 100644 index 0000000000..ee5a5dd950 --- /dev/null +++ b/devtools/client/netmonitor/src/workers/search/moz.build @@ -0,0 +1,10 @@ +# vim: set filetype=python: +# 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/. + +DevToolsModules( + "index.js", + "search.js", + "worker.js", +) diff --git a/devtools/client/netmonitor/src/workers/search/search.js b/devtools/client/netmonitor/src/workers/search/search.js new file mode 100644 index 0000000000..880f5357b2 --- /dev/null +++ b/devtools/client/netmonitor/src/workers/search/search.js @@ -0,0 +1,349 @@ +/* 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/. */ +/* eslint-disable no-unused-vars */ + +"use strict"; + +/** + * Search within specified resource. Note that this function runs + * within a worker thread. + */ +function searchInResource(resource, query, modifiers) { + const results = []; + + if (resource.url) { + results.push( + findMatches(resource, query, modifiers, { + key: "url", + label: "Url", + type: "url", + panel: "headers", + }) + ); + } + + if (resource.responseHeaders) { + results.push( + findMatches(resource, query, modifiers, { + key: "responseHeaders.headers", + type: "responseHeaders", + panel: "headers", + }) + ); + } + + if (resource.requestHeaders) { + results.push( + findMatches(resource, query, modifiers, { + key: "requestHeaders.headers", + type: "requestHeaders", + panel: "headers", + }) + ); + } + + if (resource.requestHeadersFromUploadStream) { + results.push( + findMatches(resource, query, modifiers, { + key: "requestHeadersFromUploadStream.headers", + type: "requestHeadersFromUploadStream", + panel: "headers", + }) + ); + } + + if (resource.responseCookies) { + let key = "responseCookies"; + + if (resource.responseCookies.cookies) { + key = "responseCookies.cookies"; + } + + results.push( + findMatches(resource, query, modifiers, { + key, + type: "responseCookies", + panel: "cookies", + }) + ); + } + + if (resource.requestCookies) { + let key = "requestCookies"; + + if (resource.requestCookies.cookies) { + key = "requestCookies.cookies"; + } + + results.push( + findMatches(resource, query, modifiers, { + key, + type: "requestCookies", + panel: "cookies", + }) + ); + } + + if (resource.responseContent) { + results.push( + findMatches(resource, query, modifiers, { + key: "responseContent.content.text", + type: "responseContent", + panel: "response", + }) + ); + } + + if (resource.requestPostData) { + results.push( + findMatches(resource, query, modifiers, { + key: "requestPostData.postData.text", + type: "requestPostData", + panel: "request", + }) + ); + } + + return getResults(results, resource); +} + +/** + * Concatenates all results + * @param results + * @returns {*[]} + */ +function getResults(results, resource) { + const tempResults = [].concat.apply([], results); + + // Generate unique result keys + tempResults.forEach((result, index) => { + result.key = index; + result.parentResource = resource; + }); + + return tempResults; +} + +function find(query, modifiers, source) { + const { caseSensitive } = modifiers; + const value = caseSensitive ? source : source.toLowerCase(); + const q = caseSensitive ? query : query.toLowerCase(); + + return value.includes(q); +} + +/** + * Find query matches in arrays, objects and strings. + * @param resource + * @param query + * @param modifiers + * @param data + * @returns {*[]|[]|Array|*} + */ +function findMatches(resource, query, modifiers, data) { + if (!resource || !query || !modifiers || !data) { + return []; + } + + const resourceValue = getValue(data.key, resource); + const resourceType = getType(resourceValue); + + if (resource.hasOwnProperty("name") && resource.hasOwnProperty("value")) { + return searchInProperties(query, modifiers, resource, data); + } + + switch (resourceType) { + case "string": + return searchInText(query, modifiers, resourceValue, data); + case "array": + return searchInArray(query, modifiers, resourceValue, data); + case "object": + return searchInObject(query, modifiers, resourceValue, data); + default: + return []; + } +} + +function searchInProperties(query, modifiers, obj, data) { + const { name, value } = obj; + const match = { + ...data, + }; + + if (find(query, modifiers, name)) { + match.label = name; + } + + if (find(query, modifiers, name) || find(query, modifiers, value)) { + match.value = value; + match.startIndex = value.indexOf(query); + + return match; + } + + return []; +} + +/** + * Get type of resource - deals with arrays as well. + * @param resource + * @returns {*} + */ +function getType(resource) { + return Array.isArray(resource) ? "array" : typeof resource; +} + +/** + * Function returns the value of a key, included nested keys. + * @param path + * @param obj + * @returns {*} + */ +function getValue(path, obj) { + const properties = Array.isArray(path) ? path : path.split("."); + return properties.reduce((prev, curr) => prev?.[curr], obj); +} + +/** + * Search text for specific string and return all matches found + * @param query + * @param modifiers + * @param text + * @param data + * @returns {*} + */ +function searchInText(query, modifiers, text, data) { + const { type } = data; + const lines = text.split(/\r\n|\r|\n/); + const matches = []; + + // iterate through each line + lines.forEach((curr, i) => { + const { caseSensitive } = modifiers; + const flags = caseSensitive ? "g" : "gi"; + const regexQuery = RegExp( + caseSensitive ? query : query.toLowerCase(), + flags + ); + const lineMatches = []; + let singleMatch; + + while ((singleMatch = regexQuery.exec(lines[i])) !== null) { + const startIndex = regexQuery.lastIndex; + lineMatches.push(startIndex); + } + + if (lineMatches.length !== 0) { + const line = i + 1; + const match = { + ...data, + label: type !== "url" ? line + "" : "Url", + line, + startIndex: lineMatches, + }; + + match.value = + lineMatches.length === 1 + ? getTruncatedValue(lines[i], query, lineMatches[0]) + : lines[i]; + + matches.push(match); + } + }); + + return matches.length === 0 ? [] : matches; +} + +/** + * Search for query in array. + * Iterates through each array item and handles item based on type. + * @param query + * @param modifiers + * @param arr + * @param data + * @returns {*[]} + */ +function searchInArray(query, modifiers, arr, data) { + const { key, label } = data; + const matches = arr.map((match, i) => + findMatches(match, query, modifiers, { + ...data, + label: match.hasOwnProperty("name") ? match.name : label, + key: key + ".[" + i + "]", + }) + ); + + return getResults(matches); +} + +/** + * Return query match and up to 50 characters on left and right. + * (50) + [matched query] + (50) + * @param value + * @param query + * @param startIndex + * @returns {*} + */ +function getTruncatedValue(value, query, startIndex) { + const valueSize = value.length; + const indexEnd = startIndex + query.length; + + if (valueSize < 100 + query.length) { + return value; + } + + const start = value.substring(startIndex, startIndex - 50); + const end = value.substring(indexEnd, indexEnd + 50); + + return start + end; +} + +/** + * Iterates through object, including nested objects, returns all + * @param query + * @param modifiers + * @param obj + * @param data + * @returns {*|[]} + */ +function searchInObject(query, modifiers, obj, data) { + const matches = data.hasOwnProperty("collector") ? data.collector : []; + const { caseSensitive } = modifiers; + + for (const objectKey in obj) { + const objectKeyType = getType(obj[objectKey]); + + // if the value is an object, send to search in object + if (objectKeyType === "object" && Object.keys(obj[objectKey].length > 1)) { + searchInObject(query, obj[objectKey], { + ...data, + collector: matches, + }); + + continue; + } + + const value = !caseSensitive + ? obj[objectKey].toLowerCase() + : obj[objectKey]; + const key = !caseSensitive ? objectKey.toLowerCase() : objectKey; + const q = !caseSensitive ? query.toLowerCase() : query; + + if ((objectKeyType === "string" && value.includes(q)) || key.includes(q)) { + const match = { + ...data, + }; + + const startIndex = value.indexOf(q); + + match.label = objectKey; + match.startIndex = startIndex; + match.value = getTruncatedValue(obj[objectKey], query, startIndex); + + matches.push(match); + } + } + + return matches; +} diff --git a/devtools/client/netmonitor/src/workers/search/worker.js b/devtools/client/netmonitor/src/workers/search/worker.js new file mode 100644 index 0000000000..825bf34360 --- /dev/null +++ b/devtools/client/netmonitor/src/workers/search/worker.js @@ -0,0 +1,17 @@ +/* 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"; + +/* eslint-env worker */ + +/* import-globals-from search.js */ +/* import-globals-from ../../../../shared/worker-utils.js */ +importScripts( + "resource://devtools/client/netmonitor/src/workers/search/search.js", + "resource://devtools/client/shared/worker-utils.js" +); + +// Implementation of search worker (runs in worker scope). +self.onmessage = workerHandler({ searchInResource }); |