Bug 1559347 - Implement generic search across all resources. r=Honza

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
lloan 2019-08-07 15:58:21 +00:00
Родитель b05ce1a78b
Коммит 10edd3b03b
25 изменённых файлов: 919 добавлений и 6 удалений

Просмотреть файл

@ -20,6 +20,7 @@ NetMonitorPanel.prototype = {
await app.bootstrap({
toolbox: this.toolbox,
document: this.panelWin.document,
win: this.panelWin,
});
// Ready to go!

Просмотреть файл

@ -12,12 +12,14 @@ const sort = require("./sort");
const timingMarkers = require("./timing-markers");
const ui = require("./ui");
const webSockets = require("./web-sockets");
const search = require("./search");
Object.assign(
exports,
batching,
filters,
requests,
search,
selection,
sort,
timingMarkers,

Просмотреть файл

@ -7,6 +7,7 @@ DevToolsModules(
'filters.js',
'index.js',
'requests.js',
'search.js',
'selection.js',
'sort.js',
'timing-markers.js',

Просмотреть файл

@ -0,0 +1,220 @@
/* 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 {
ADD_SEARCH_QUERY,
ADD_SEARCH_RESULT,
CLEAR_SEARCH_RESULTS,
ADD_ONGOING_SEARCH,
OPEN_SEARCH,
CLOSE_SEARCH,
UPDATE_SEARCH_STATUS,
SEARCH_STATUS,
} = require("../constants");
const {
getDisplayedRequests,
getOngoingSearch,
getSearchStatus,
getRequestById,
} = require("../selectors/index");
const { fetchNetworkUpdatePacket } = require("../utils/request-utils");
const { searchInResource } = require("../workers/search/index");
/**
* Search through all resources. This is the main action exported
* from this module and consumed by Network panel UI.
*/
function search(connector, query) {
let cancelled = false;
// Instantiate an `ongoingSearch` function/object. It's responsible
// for triggering set of asynchronous steps like fetching
// data from the backend and performing search over it.
// This `ongoingSearch` is stored in the Search reducer, so it can
// be canceled if needed (e.g. when new search is executed).
const newOngoingSearch = async (dispatch, getState) => {
const state = getState();
dispatch(stopOngoingSearch());
await dispatch(addOngoingSearch(newOngoingSearch));
await dispatch(clearSearchResults());
await dispatch(addSearchQuery(query));
dispatch(updateSearchStatus(SEARCH_STATUS.FETCHING));
// Loop over all displayed resources (in the sorted order),
// fetch all the details data and run search worker that
// search through the resource structure.
const requests = getDisplayedRequests(state);
for (const request of requests) {
if (cancelled) {
return;
}
// Fetch all data for the resource.
await loadResource(connector, request);
// The state changed, so make sure to get fresh new reference
// to the updated resource object.
const updatedResource = getRequestById(getState(), request.id);
await dispatch(searchResource(updatedResource, query));
}
dispatch(updateSearchStatus(SEARCH_STATUS.DONE));
};
// Implement support for canceling (used e.g. when a new search
// is executed or the user stops the searching manually).
newOngoingSearch.cancel = () => {
cancelled = true;
};
return newOngoingSearch;
}
/**
* Fetch all data related to the specified resource from the backend.
*/
async function loadResource(connector, resource) {
const updateTypes = [
"responseHeaders",
"requestHeaders",
"responseCookies",
"requestCookies",
"requestPostData",
"responseContent",
"responseCache",
"stackTrace",
"securityInfo",
];
return fetchNetworkUpdatePacket(connector.requestData, resource, updateTypes);
}
/**
* Search through all data within the specified resource.
*/
function searchResource(resource, query) {
return async (dispatch, getState) => {
// Run search in a worker and wait for the results. The return
// value is an array with search occurrences.
const result = await searchInResource(resource, query);
if (!result.length) {
return;
}
dispatch(addSearchResult(resource, result, query));
};
}
/**
* Add search query to the reducer.
*/
function addSearchResult(resource, result) {
return {
type: ADD_SEARCH_RESULT,
resource,
result,
};
}
/**
* Add search query to the reducer.
*/
function addSearchQuery(query) {
return {
type: ADD_SEARCH_QUERY,
query,
};
}
/**
* Clear all search results.
*/
function clearSearchResults() {
return {
type: CLEAR_SEARCH_RESULTS,
};
}
/**
* Update status of the current search.
*/
function updateSearchStatus(status) {
return {
type: UPDATE_SEARCH_STATUS,
status,
};
}
/**
* Close the entire search panel.
*/
function closeSearch() {
return (dispatch, getState) => {
dispatch(stopOngoingSearch());
dispatch({ type: CLOSE_SEARCH });
};
}
function openSearch() {
return (dispatch, getState) => {
dispatch({ type: OPEN_SEARCH });
};
}
/**
* Toggle visibility of search panel in network panel
*/
function toggleSearchPanel() {
return (dispatch, getState) => {
const state = getState();
state.search.panelOpen
? dispatch({ type: CLOSE_SEARCH })
: dispatch({ type: OPEN_SEARCH });
};
}
/**
* Append new search object into the reducer. The search object
* is cancellable and so, it implements `cancel` method.
*/
function addOngoingSearch(ongoingSearch) {
return {
type: ADD_ONGOING_SEARCH,
ongoingSearch,
};
}
/**
* Cancel the current ongoing search.
*/
function stopOngoingSearch() {
return (dispatch, getState) => {
const state = getState();
const ongoingSearch = getOngoingSearch(state);
const status = getSearchStatus(state);
if (ongoingSearch && status !== SEARCH_STATUS.DONE) {
ongoingSearch.cancel();
dispatch(updateSearchStatus(SEARCH_STATUS.CANCELLED));
}
};
}
module.exports = {
search,
closeSearch,
openSearch,
clearSearchResults,
addSearchQuery,
toggleSearchPanel,
};

Просмотреть файл

@ -17,6 +17,10 @@ const { EVENTS } = require("./constants");
const { getDisplayedRequestById } = require("./selectors/index");
const SearchWorker = require("./workers/search/index");
const SEARCH_WORKER_URL =
"resource://devtools/client/netmonitor/src/workers/search/worker.js";
/**
* Global App object for Network panel. This object depends
* on the UI and can't be created independently.
@ -31,7 +35,7 @@ function NetMonitorApp(api) {
}
NetMonitorApp.prototype = {
async bootstrap({ toolbox, document }) {
async bootstrap({ toolbox, document, win }) {
// Get the root element for mounting.
this.mount = document.querySelector("#mount");
@ -50,6 +54,9 @@ NetMonitorApp.prototype = {
});
};
// Bootstrap search worker
SearchWorker.start(SEARCH_WORKER_URL, win);
const { actions, connector, store } = this.api;
const sourceMapService = toolbox.sourceMapURLService;
@ -71,6 +78,8 @@ NetMonitorApp.prototype = {
destroy() {
unmountComponentAtNode(this.mount);
SearchWorker.stop();
// Make sure to destroy the API object. It's usually destroyed
// in the Toolbox destroy method, but we need it here for case
// where the Network panel is initialized without the toolbox

Просмотреть файл

@ -88,6 +88,8 @@ class RequestListContent extends Component {
selectedRequest: PropTypes.object,
unblockSelectedRequestURL: PropTypes.func.isRequired,
requestFilterTypes: PropTypes.object.isRequired,
panelOpen: PropTypes.bool,
toggleSearchPanel: PropTypes.func.isRequired,
};
}
@ -310,6 +312,8 @@ class RequestListContent extends Component {
sendCustomRequest,
openStatistics,
unblockSelectedRequestURL,
toggleSearchPanel,
panelOpen,
} = this.props;
this.contextMenu = new RequestListContextMenu({
blockSelectedRequestURL,
@ -320,6 +324,8 @@ class RequestListContent extends Component {
openStatistics,
openRequestInTab: this.openRequestInTab,
unblockSelectedRequestURL,
toggleSearchPanel,
panelOpen,
});
}
@ -410,6 +416,7 @@ module.exports = connect(
selectedRequest: getSelectedRequest(state),
scale: getWaterfallScale(state),
requestFilterTypes: state.filters.requestFilterTypes,
panelOpen: state.search.panelOpen,
}),
(dispatch, props) => ({
blockSelectedRequestURL: clickedRequest => {
@ -456,5 +463,6 @@ module.exports = connect(
onWaterfallMouseDown: () => {
dispatch(Actions.selectDetailsPanelTab("timings"));
},
toggleSearchPanel: () => dispatch(Actions.toggleSearchPanel()),
})
)(RequestListContent);

Просмотреть файл

@ -42,6 +42,24 @@ const actionTypes = {
WS_SET_REQUEST_FILTER_TEXT: "WS_SET_REQUEST_FILTER_TEXT",
WS_TOGGLE_COLUMN: "WS_TOGGLE_COLUMN",
WS_RESET_COLUMNS: "WS_RESET_COLUMNS",
// Search
ADD_SEARCH_QUERY: "ADD_SEARCH_QUERY",
ADD_SEARCH_RESULT: "ADD_SEARCH_RESULT",
CLEAR_SEARCH_RESULTS: "CLEAR_SEARCH_RESULTS",
ADD_ONGOING_SEARCH: "ADD_ONGOING_SEARCH",
OPEN_SEARCH: "OPEN_SEARCH",
CLOSE_SEARCH: "CLOSE_SEARCH",
UPDATE_SEARCH_STATUS: "UPDATE_SEARCH_STATUS",
};
// Search status types
const SEARCH_STATUS = {
INITIAL: "INITIAL",
FETCHING: "FETCHING",
CANCELLED: "CANCELLED",
DONE: "DONE",
ERROR: "ERROR",
};
// Descriptions for what this frontend is currently doing.
@ -488,6 +506,7 @@ const general = {
DEFAULT_COLUMN_WIDTH,
SUPPORTED_HTTP_CODES,
BLOCKED_REASON_MESSAGES,
SEARCH_STATUS,
};
// flatten constants

Просмотреть файл

@ -31,6 +31,7 @@ const {
WebSockets,
getWebSocketsDefaultColumnsState,
} = require("./reducers/web-sockets");
const { Search } = require("./reducers/search");
/**
* Configure state and middleware for the Network monitor tool.
@ -51,6 +52,7 @@ function configureStore(connector, telemetry) {
webSockets: WebSockets({
columns: getWebSocketsColumnState(),
}),
search: new Search(),
};
// Prepare middleware.

Просмотреть файл

@ -12,6 +12,7 @@ DIRS += [
'selectors',
'utils',
'widgets',
'workers',
]
DevToolsModules(

Просмотреть файл

@ -7,6 +7,7 @@
const { combineReducers } = require("devtools/client/shared/vendor/redux");
const batchingReducer = require("./batching");
const { requestsReducer } = require("./requests");
const { search } = require("./search");
const { sortReducer } = require("./sort");
const { filters } = require("./filters");
const { timingMarkers } = require("./timing-markers");
@ -17,6 +18,7 @@ const networkThrottling = require("devtools/client/shared/components/throttling/
module.exports = batchingReducer(
combineReducers({
requests: requestsReducer,
search,
sort: sortReducer,
webSockets,
filters,

Просмотреть файл

@ -7,6 +7,7 @@ DevToolsModules(
'filters.js',
'index.js',
'requests.js',
'search.js',
'sort.js',
'timing-markers.js',
'ui.js',

Просмотреть файл

@ -0,0 +1,116 @@
/* 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 {
ADD_SEARCH_QUERY,
ADD_SEARCH_RESULT,
CLEAR_SEARCH_RESULTS,
ADD_ONGOING_SEARCH,
OPEN_SEARCH,
CLOSE_SEARCH,
SEARCH_STATUS,
UPDATE_SEARCH_STATUS,
} = require("../constants");
/**
* Search reducer stores the following data:
* - query [String]: the string the user is looking for
* - results [Object]: the list of search results
* - ongoingSearch [Object]: the object representing the current search
* - status [String]: status of the current search (see constants.js)
*/
function Search(overrideParams = {}) {
return Object.assign(
{
query: "",
results: [],
ongoingSearch: null,
status: SEARCH_STATUS.INITIAL,
panelOpen: false,
},
overrideParams
);
}
function search(state = new Search(), action) {
switch (action.type) {
case ADD_SEARCH_QUERY:
return onAddSearchQuery(state, action);
case ADD_SEARCH_RESULT:
return onAddSearchResult(state, action);
case CLEAR_SEARCH_RESULTS:
return onClearSearchResults(state);
case ADD_ONGOING_SEARCH:
return onAddOngoingSearch(state, action);
case CLOSE_SEARCH:
return onCloseSearch(state);
case OPEN_SEARCH:
return onOpenSearch(state);
case UPDATE_SEARCH_STATUS:
return onUpdateSearchStatus(state, action);
}
return state;
}
function onAddSearchQuery(state, action) {
return {
...state,
query: action.query,
};
}
function onAddSearchResult(state, action) {
const results = state.results.slice();
results.push({
resource: action.resource,
results: action.result,
});
return {
...state,
results,
};
}
function onClearSearchResults(state) {
return {
...state,
results: [],
};
}
function onAddOngoingSearch(state, action) {
return {
...state,
ongoingSearch: action.ongoingSearch,
};
}
function onCloseSearch(state) {
return {
...state,
panelOpen: false,
};
}
function onOpenSearch(state) {
return {
...state,
panelOpen: true,
};
}
function onUpdateSearchStatus(state, action) {
return {
...state,
status: action.status,
};
}
module.exports = {
Search,
search,
};

Просмотреть файл

@ -5,8 +5,9 @@
"use strict";
const requests = require("./requests");
const search = require("./search");
const timingMarkers = require("./timing-markers");
const ui = require("./ui");
const webSockets = require("./web-sockets");
Object.assign(exports, requests, timingMarkers, ui, webSockets);
Object.assign(exports, search, requests, timingMarkers, ui, webSockets);

Просмотреть файл

@ -5,6 +5,7 @@
DevToolsModules(
'index.js',
'requests.js',
'search.js',
'timing-markers.js',
'ui.js',
'web-sockets.js',

Просмотреть файл

@ -0,0 +1,18 @@
/* 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";
function getOngoingSearch(state) {
return state.search.ongoingSearch;
}
function getSearchStatus(state) {
return state.search.status;
}
module.exports = {
getOngoingSearch,
getSearchStatus,
};

Просмотреть файл

@ -89,19 +89,22 @@ async function fetchHeaders(headers, getLongString) {
* @param {array} updateTypes - a list of network event update types
*/
function fetchNetworkUpdatePacket(requestData, request, updateTypes) {
const promises = [];
updateTypes.forEach(updateType => {
// Only stackTrace will be handled differently
if (updateType === "stackTrace") {
if (request.cause.stacktraceAvailable && !request.stacktrace) {
requestData(request.id, updateType);
promises.push(requestData(request.id, updateType));
}
return;
}
if (request[`${updateType}Available`] && !request[updateType]) {
requestData(request.id, updateType);
promises.push(requestData(request.id, updateType));
}
});
return Promise.all(promises);
}
/**

Просмотреть файл

@ -340,12 +340,29 @@ class RequestListContextMenu {
this.useAsFetch(id, url, method, requestHeaders, requestPostData),
});
if (Services.prefs.getBoolPref("devtools.netmonitor.features.search")) {
const { toggleSearchPanel, panelOpen } = this.props;
menu.push({
type: "separator",
});
menu.push({
id: "request-list-context-search",
label: "Search...", // TODO localization
accesskey: "S", // TODO localization
type: "checkbox",
checked: panelOpen,
visible: !!clickedRequest,
click: () => toggleSearchPanel(),
});
}
showMenu(menu, {
screenX: event.screenX,
screenY: event.screenY,
});
}
/* eslint-enable complexity */
/**
* Opens selected item in the debugger

Просмотреть файл

@ -0,0 +1,12 @@
# 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',
]
DevToolsModules(
'worker-utils.js',
)

Просмотреть файл

@ -0,0 +1,21 @@
/* 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("../worker-utils");
const dispatcher = new WorkerDispatcher();
const start = dispatcher.start.bind(dispatcher);
const stop = dispatcher.stop.bind(dispatcher);
// The search worker support just one task at this point,
// which is searching through specified resource.
const searchInResource = dispatcher.task("searchInResource");
module.exports = {
start,
stop,
searchInResource,
};

Просмотреть файл

@ -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',
)

Просмотреть файл

@ -0,0 +1,306 @@
/* 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) {
const results = [];
if (resource.url) {
results.push(
findMatches(resource, query, {
key: "url",
label: "Url",
type: "url",
})
);
}
if (resource.responseHeaders) {
results.push(
findMatches(resource, query, {
key: "responseHeaders.headers",
label: "Response Headers",
type: "responseHeaders",
})
);
}
if (resource.requestHeaders) {
results.push(
findMatches(resource, query, {
key: "requestHeaders.headers",
label: "Request Headers",
type: "requestHeaders",
})
);
}
if (resource.responseCookies) {
let key = "responseCookies";
if (resource.responseCookies.cookies) {
key = "responseCookies.cookies";
}
results.push(
findMatches(resource, query, {
key,
label: "Response Cookies",
type: "responseCookies",
})
);
}
if (resource.requestCookies) {
let key = "requestCookies";
if (resource.requestCookies.cookies) {
key = "requestCookies.cookies";
}
results.push(
findMatches(resource, query, {
key,
label: "Request Cookies",
type: "requestCookies",
})
);
}
if (resource.securityInfo) {
results.push(
findMatches(resource, query, {
key: "securityInfo",
label: "Security Information",
type: "securityInfo",
})
);
}
if (resource.responseContent) {
results.push(
findMatches(resource, query, {
key: "responseContent.content.text",
label: "Response Content",
type: "responseContent",
})
);
}
if (resource.requestPostData) {
results.push(
findMatches(resource, query, {
key: "requestPostData.postData.text",
label: "Request Post Data",
type: "requestPostData",
})
);
}
return getResults(results);
}
/**
* Concatenates all results
* @param results
* @returns {*[]}
*/
function getResults(results) {
const tempResults = [].concat.apply([], results);
// Generate unique result keys
tempResults.forEach((result, index) => {
result.key = index;
});
return tempResults;
}
/**
* Find query matches in arrays, objects and strings.
* @param resource
* @param query
* @param data
* @returns {*}
*/
function findMatches(resource, query, data) {
if (!resource || !query || !data) {
return [];
}
const resourceValue = getValue(data.key, resource);
const resourceType = getType(resourceValue);
switch (resourceType) {
case "string":
return searchInText(query, resourceValue, data);
case "array":
return searchInArray(query, resourceValue, data);
case "object":
return searchInObject(query, resourceValue, data);
default:
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 && prev[curr], obj);
}
/**
* Search text for specific string and return all matches found
* @param query
* @param text
* @param data
* @returns {*}
*/
function searchInText(query, text, data) {
const { type, label } = data;
const lines = text.split(/\r\n|\r|\n/);
const matches = [];
// iterate through each line
lines.forEach((curr, i, arr) => {
const regexQuery = RegExp(query, "gmi");
let singleMatch;
while ((singleMatch = regexQuery.exec(lines[i])) !== null) {
const startIndex = regexQuery.lastIndex;
matches.push({
type,
label,
line: i,
value: getTruncatedValue(lines[i], query, startIndex),
startIndex,
});
}
});
return matches.length === 0 ? [] : matches;
}
/**
* Search for query in array.
* Iterates through each array item and handles item based on type.
* @param query
* @param arr
* @param data
* @returns {*}
*/
function searchInArray(query, arr, data) {
const { type, key, label } = data;
const matches = arr
.filter(match => JSON.stringify(match).includes(query))
.map((match, i) =>
findMatches(match, query, {
label,
key: key + ".[" + i + "]",
type,
})
);
return getResults(matches);
}
/**
* Return query match and up to 100 characters on left and right.
* (100) + [matched query] + (100)
* @param value
* @param query
* @param startIndex
* @returns {*}
*/
function getTruncatedValue(value, query, startIndex) {
const valueSize = value.length;
const indexEnd = startIndex + query.length;
if (valueSize < 200 + query.length) {
return value;
}
const start = value.substring(startIndex, startIndex - 100);
const end = value.substring(indexEnd, indexEnd + 100);
return start + query + end;
}
/**
* Iterates through object, including nested objects, returns all
* found matches.
* @param query
* @param obj
* @param data
* @returns {*}
*/
function searchInObject(query, obj, data) {
const { type, label } = data;
const matches = data.hasOwnProperty("collector") ? data.collector : [];
for (const objectKey in obj) {
if (obj.hasOwnProperty(objectKey)) {
if (typeof obj[objectKey] === "object") {
searchInObject(obj[objectKey], query, {
collector: matches,
type,
label: objectKey,
});
}
let location;
if (objectKey) {
if (objectKey.includes(query)) {
location = "name";
} else if (
typeof obj[objectKey] === "string" &&
obj[objectKey].includes(query)
) {
location = "value";
}
}
if (!location) {
continue;
}
const match = {
type,
label: objectKey,
};
const value = location === "name" ? objectKey : obj[objectKey];
const startIndex = value.indexOf(query);
match.startIndex = startIndex;
match.value = getTruncatedValue(value, query, startIndex);
matches.push(match);
}
}
return matches;
}

Просмотреть файл

@ -0,0 +1,11 @@
/* 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-disable no-undef*/
importScripts("./search.js", "../worker-utils.js");
// Implementation of search worker (runs in worker scope).
self.onmessage = workerHandler({ searchInResource });

Просмотреть файл

@ -0,0 +1,128 @@
/* 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";
function WorkerDispatcher() {
this.msgId = 1;
this.worker = null;
}
WorkerDispatcher.prototype = {
start(url, win) {
this.worker = new win.Worker(url);
this.worker.onerror = () => {
console.error(`Error in worker ${url}`);
};
},
stop() {
if (!this.worker) {
return;
}
this.worker.terminate();
this.worker = null;
},
task(method, { queue = false } = {}) {
const calls = [];
const push = args => {
return new Promise((resolve, reject) => {
if (queue && calls.length === 0) {
Promise.resolve().then(flush);
}
calls.push([args, resolve, reject]);
if (!queue) {
flush();
}
});
};
const flush = () => {
const items = calls.slice();
calls.length = 0;
if (!this.worker) {
return;
}
const id = this.msgId++;
this.worker.postMessage({
id,
method,
calls: items.map(item => item[0]),
});
const listener = ({ data: result }) => {
if (result.id !== id) {
return;
}
if (!this.worker) {
return;
}
this.worker.removeEventListener("message", listener);
result.results.forEach((resultData, i) => {
const [, resolve, reject] = items[i];
if (resultData.error) {
reject(resultData.error);
} else {
resolve(resultData.response);
}
});
};
this.worker.addEventListener("message", listener);
};
return (...args) => push(args);
},
invoke(method, ...args) {
return this.task(method)(...args);
},
};
function workerHandler(publicInterface) {
return function(msg) {
const { id, method, calls } = msg.data;
Promise.all(
calls.map(args => {
try {
const response = publicInterface[method].apply(undefined, args);
if (response instanceof Promise) {
return response.then(
val => ({ response: val }),
// Error can't be sent via postMessage, so be sure to
// convert to string.
err => ({ error: err.toString() })
);
}
return { response };
} catch (error) {
// Error can't be sent via postMessage, so be sure to convert to
// string.
return { error: error.toString() };
}
})
).then(results => {
self.postMessage({ id, results });
});
};
}
// Might be loaded within a worker thread where `module` isn't available.
if (typeof module !== "undefined") {
module.exports = {
WorkerDispatcher,
workerHandler,
};
}

Просмотреть файл

@ -163,6 +163,9 @@ pref("devtools.serviceWorkers.testing.enabled", false);
// Enable the Network Monitor
pref("devtools.netmonitor.enabled", true);
// Enable Network Search
pref("devtools.netmonitor.features.search", false);
// Enable the Application panel
pref("devtools.application.enabled", false);

Просмотреть файл

@ -139,7 +139,7 @@ class SearchBox extends PureComponent {
onKeyDown(e) {
if (this.props.onKeyDown) {
this.props.onKeyDown();
this.props.onKeyDown(e);
}
const autocomplete = this.autocompleteRef.current;