diff options
Diffstat (limited to 'devtools/client/debugger/src/actions/utils/middleware')
7 files changed, 330 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/utils/middleware/context.js b/devtools/client/debugger/src/actions/utils/middleware/context.js new file mode 100644 index 0000000000..ebadaa4eff --- /dev/null +++ b/devtools/client/debugger/src/actions/utils/middleware/context.js @@ -0,0 +1,33 @@ +/* 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/>. */ + +import { + validateNavigateContext, + validateContext, +} from "../../../utils/context"; + +function validateActionContext(getState, action) { + if (action.type == "COMMAND" && action.status == "done") { + // The thread will have resumed execution since the action was initiated, + // so just make sure we haven't navigated. + validateNavigateContext(getState(), action.cx); + return; + } + + // Validate using all available information in the context. + validateContext(getState(), action.cx); +} + +// Middleware which looks for actions that have a cx property and ignores +// them if the context is no longer valid. +function context({ dispatch, getState }) { + return next => action => { + if ("cx" in action) { + validateActionContext(getState, action); + } + return next(action); + }; +} + +export { context }; diff --git a/devtools/client/debugger/src/actions/utils/middleware/log.js b/devtools/client/debugger/src/actions/utils/middleware/log.js new file mode 100644 index 0000000000..b9592ce22c --- /dev/null +++ b/devtools/client/debugger/src/actions/utils/middleware/log.js @@ -0,0 +1,111 @@ +/* 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/>. */ + +import flags from "devtools/shared/flags"; +import { prefs } from "../../../utils/prefs"; + +const ignoreList = [ + "ADD_BREAKPOINT_POSITIONS", + "SET_SYMBOLS", + "OUT_OF_SCOPE_LOCATIONS", + "MAP_SCOPES", + "MAP_FRAMES", + "ADD_SCOPES", + "IN_SCOPE_LINES", + "REMOVE_BREAKPOINT", + "NODE_PROPERTIES_LOADED", + "SET_FOCUSED_SOURCE_ITEM", + "NODE_EXPAND", + "IN_SCOPE_LINES", + "SET_PREVIEW", +]; + +function cloneAction(action) { + action = action || {}; + action = { ...action }; + + // ADD_TAB, ... + if (action.source?.text) { + const source = { ...action.source, text: "" }; + action.source = source; + } + + if (action.sources) { + const sources = action.sources.slice(0, 20).map(source => { + const url = !source.url || source.url.includes("data:") ? "" : source.url; + return { ...source, url }; + }); + action.sources = sources; + } + + // LOAD_SOURCE_TEXT + if (action.text) { + action.text = ""; + } + + if (action.value?.text) { + const value = { ...action.value, text: "" }; + action.value = value; + } + + return action; +} + +function formatPause(pause) { + return { + ...pause, + pauseInfo: { why: pause.why }, + scopes: [], + loadedObjects: [], + }; +} + +function serializeAction(action) { + try { + action = cloneAction(action); + if (ignoreList.includes(action.type)) { + action = {}; + } + + if (action.type === "PAUSED") { + action = formatPause(action); + } + + const serializer = function (key, value) { + // Serialize Object/LongString fronts + if (value?.getGrip) { + return value.getGrip(); + } + return value; + }; + + // dump(`> ${action.type}...\n ${JSON.stringify(action, serializer)}\n`); + return JSON.stringify(action, serializer); + } catch (e) { + console.error(e); + return ""; + } +} + +/** + * A middleware that logs all actions coming through the system + * to the console. + */ +export function log({ dispatch, getState }) { + return next => action => { + const asyncMsg = !action.status ? "" : `[${action.status}]`; + + if (prefs.logActions) { + if (flags.testing) { + dump( + `[ACTION] ${action.type} ${asyncMsg} - ${serializeAction(action)}\n` + ); + } else { + console.log(action, asyncMsg); + } + } + + next(action); + }; +} diff --git a/devtools/client/debugger/src/actions/utils/middleware/moz.build b/devtools/client/debugger/src/actions/utils/middleware/moz.build new file mode 100644 index 0000000000..f46a0bb725 --- /dev/null +++ b/devtools/client/debugger/src/actions/utils/middleware/moz.build @@ -0,0 +1,15 @@ +# 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 += [] + +CompiledModules( + "context.js", + "log.js", + "promise.js", + "thunk.js", + "timing.js", + "wait-service.js", +) diff --git a/devtools/client/debugger/src/actions/utils/middleware/promise.js b/devtools/client/debugger/src/actions/utils/middleware/promise.js new file mode 100644 index 0000000000..52054a1fcc --- /dev/null +++ b/devtools/client/debugger/src/actions/utils/middleware/promise.js @@ -0,0 +1,61 @@ +/* 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/>. */ + +import { executeSoon } from "../../../utils/DevToolsUtils"; + +import { pending, rejected, fulfilled } from "../../../utils/async-value"; +export function asyncActionAsValue(action) { + if (action.status === "start") { + return pending(); + } + if (action.status === "error") { + return rejected(action.error); + } + return fulfilled(action.value); +} + +let seqIdVal = 1; + +function seqIdGen() { + return seqIdVal++; +} + +function promiseMiddleware({ dispatch, getState }) { + return next => action => { + if (!(PROMISE in action)) { + return next(action); + } + + const seqId = seqIdGen().toString(); + const { [PROMISE]: promiseInst, ...originalActionProperties } = action; + + // Create a new action that doesn't have the promise field and has + // the `seqId` field that represents the sequence id + action = { ...originalActionProperties, seqId }; + + dispatch({ ...action, status: "start" }); + + // Return the promise so action creators can still compose if they + // want to. + return Promise.resolve(promiseInst) + .finally(() => new Promise(resolve => executeSoon(resolve))) + .then( + value => { + dispatch({ ...action, status: "done", value: value }); + return value; + }, + error => { + dispatch({ + ...action, + status: "error", + error: error.message || error, + }); + return Promise.reject(error); + } + ); + }; +} + +export const PROMISE = "@@dispatch/promise"; +export { promiseMiddleware as promise }; diff --git a/devtools/client/debugger/src/actions/utils/middleware/thunk.js b/devtools/client/debugger/src/actions/utils/middleware/thunk.js new file mode 100644 index 0000000000..fba17d516c --- /dev/null +++ b/devtools/client/debugger/src/actions/utils/middleware/thunk.js @@ -0,0 +1,22 @@ +/* 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/>. */ + +/** + * A middleware that allows thunks (functions) to be dispatched. If + * it's a thunk, it is called with an argument that contains + * `dispatch`, `getState`, and any additional args passed in via the + * middleware constructure. This allows the action to create multiple + * actions (most likely asynchronously). + */ +export function thunk(makeArgs) { + return ({ dispatch, getState }) => { + const args = { dispatch, getState }; + + return next => action => { + return typeof action === "function" + ? action(makeArgs ? makeArgs(args, getState()) : args) + : next(action); + }; + }; +} diff --git a/devtools/client/debugger/src/actions/utils/middleware/timing.js b/devtools/client/debugger/src/actions/utils/middleware/timing.js new file mode 100644 index 0000000000..d0bfa05977 --- /dev/null +++ b/devtools/client/debugger/src/actions/utils/middleware/timing.js @@ -0,0 +1,26 @@ +/* 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/>. */ + +/** + * Redux middleware that sets performance markers for all actions such that they + * will appear in performance tooling under the User Timing API + */ + +const mark = window.performance?.mark + ? window.performance.mark.bind(window.performance) + : a => {}; + +const measure = window.performance?.measure + ? window.performance.measure.bind(window.performance) + : (a, b, c) => {}; + +export function timing(store) { + return next => action => { + mark(`${action.type}_start`); + const result = next(action); + mark(`${action.type}_end`); + measure(`${action.type}`, `${action.type}_start`, `${action.type}_end`); + return result; + }; +} diff --git a/devtools/client/debugger/src/actions/utils/middleware/wait-service.js b/devtools/client/debugger/src/actions/utils/middleware/wait-service.js new file mode 100644 index 0000000000..337df7e336 --- /dev/null +++ b/devtools/client/debugger/src/actions/utils/middleware/wait-service.js @@ -0,0 +1,62 @@ +/* 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/>. */ + +/** + * A middleware which acts like a service, because it is stateful + * and "long-running" in the background. It provides the ability + * for actions to install a function to be run once when a specific + * condition is met by an action coming through the system. Think of + * it as a thunk that blocks until the condition is met. Example: + * + * ```js + * const services = { WAIT_UNTIL: require('wait-service').NAME }; + * + * { type: services.WAIT_UNTIL, + * predicate: action => action.type === "ADD_ITEM", + * run: (dispatch, getState, action) => { + * // Do anything here. You only need to accept the arguments + * // if you need them. `action` is the action that satisfied + * // the predicate. + * } + * } + * ``` + */ +export const NAME = "@@service/waitUntil"; + +export function waitUntilService({ dispatch, getState }) { + let pending = []; + + function checkPending(action) { + const readyRequests = []; + const stillPending = []; + + // Find the pending requests whose predicates are satisfied with + // this action. Wait to run the requests until after we update the + // pending queue because the request handler may synchronously + // dispatch again and run this service (that use case is + // completely valid). + for (const request of pending) { + if (request.predicate(action)) { + readyRequests.push(request); + } else { + stillPending.push(request); + } + } + + pending = stillPending; + for (const request of readyRequests) { + request.run(dispatch, getState, action); + } + } + + return next => action => { + if (action.type === NAME) { + pending.push(action); + return null; + } + const result = next(action); + checkPending(action); + return result; + }; +} |