summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/actions/utils
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/debugger/src/actions/utils')
-rw-r--r--devtools/client/debugger/src/actions/utils/create-store.js75
-rw-r--r--devtools/client/debugger/src/actions/utils/middleware/context.js90
-rw-r--r--devtools/client/debugger/src/actions/utils/middleware/log.js111
-rw-r--r--devtools/client/debugger/src/actions/utils/middleware/moz.build15
-rw-r--r--devtools/client/debugger/src/actions/utils/middleware/promise.js61
-rw-r--r--devtools/client/debugger/src/actions/utils/middleware/thunk.js22
-rw-r--r--devtools/client/debugger/src/actions/utils/middleware/timing.js26
-rw-r--r--devtools/client/debugger/src/actions/utils/middleware/wait-service.js62
-rw-r--r--devtools/client/debugger/src/actions/utils/moz.build12
9 files changed, 474 insertions, 0 deletions
diff --git a/devtools/client/debugger/src/actions/utils/create-store.js b/devtools/client/debugger/src/actions/utils/create-store.js
new file mode 100644
index 0000000000..5de4fa74d0
--- /dev/null
+++ b/devtools/client/debugger/src/actions/utils/create-store.js
@@ -0,0 +1,75 @@
+/* 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/>. */
+
+/* global window */
+
+/**
+ * Redux store utils
+ * @module utils/create-store
+ */
+
+import {
+ createStore,
+ applyMiddleware,
+} from "devtools/client/shared/vendor/redux";
+import { waitUntilService } from "./middleware/wait-service";
+import { log } from "./middleware/log";
+import { promise } from "./middleware/promise";
+import { thunk } from "./middleware/thunk";
+import { timing } from "./middleware/timing";
+import { context } from "./middleware/context";
+
+/**
+ * @memberof utils/create-store
+ * @static
+ */
+
+/**
+ * This creates a dispatcher with all the standard middleware in place
+ * that all code requires. It can also be optionally configured in
+ * various ways, such as logging and recording.
+ *
+ * @param {object} opts:
+ * - log: log all dispatched actions to console
+ * - history: an array to store every action in. Should only be
+ * used in tests.
+ * - middleware: array of middleware to be included in the redux store
+ * @memberof utils/create-store
+ * @static
+ */
+const configureStore = (opts = {}) => {
+ const middleware = [
+ thunk(opts.makeThunkArgs),
+ context,
+ promise,
+
+ // Order is important: services must go last as they always
+ // operate on "already transformed" actions. Actions going through
+ // them shouldn't have any special fields like promises, they
+ // should just be normal JSON objects.
+ waitUntilService,
+ ];
+
+ if (opts.middleware) {
+ opts.middleware.forEach(fn => middleware.push(fn));
+ }
+
+ if (opts.log) {
+ middleware.push(log);
+ }
+
+ if (opts.timing) {
+ middleware.push(timing);
+ }
+
+ // Hook in the redux devtools browser extension if it exists
+ const devtoolsExt =
+ typeof window === "object" && window.devToolsExtension
+ ? window.devToolsExtension()
+ : f => f;
+
+ return applyMiddleware(...middleware)(devtoolsExt(createStore));
+};
+
+export default configureStore;
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..00711a8c3f
--- /dev/null
+++ b/devtools/client/debugger/src/actions/utils/middleware/context.js
@@ -0,0 +1,90 @@
+/* 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,
+ validateSelectedFrame,
+ validateBreakpoint,
+ validateSource,
+ validateSourceActor,
+ validateThreadFrames,
+ validateFrame,
+} 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);
+ }
+
+ // Validate actions specific to a Source object.
+ // This will throw if the source has been removed,
+ // i.e. when the source has been removed from all the threads where it existed.
+ if ("source" in action) {
+ validateSource(getState(), action.source);
+ }
+
+ // Validate actions specific to a Source Actor object.
+ // This will throw if the source actor has been removed,
+ // i.e. when the source actor's thread has been removed.
+ if ("sourceActor" in action) {
+ validateSourceActor(getState(), action.sourceActor);
+ }
+
+ // Similar to sourceActor assertion, but with a distinct attribute name
+ if ("generatedSourceActor" in action) {
+ validateSourceActor(getState(), action.generatedSourceActor);
+ }
+
+ // Validate actions specific to a given breakpoint.
+ // This will throw if the breakpoint's location is obsolete.
+ // i.e. when the related source has been removed.
+ if ("breakpoint" in action) {
+ validateBreakpoint(getState(), action.breakpoint);
+ }
+
+ // Validate actions specific to the currently selected paused frame.
+ // It will throw if we resumed or moved to another frame in the call stack.
+ //
+ // Ignore falsy selectedFrame as sometimes it can be null
+ // for expression actions.
+ if (action.selectedFrame) {
+ validateSelectedFrame(getState(), action.selectedFrame);
+ }
+
+ // Validate actions specific to a given pause location.
+ // This will throw if we resumed or paused in another location.
+ // Compared to selected frame, this would not throw if we moved to another frame in the call stack.
+ if ("thread" in action && "frames" in action) {
+ validateThreadFrames(getState(), action.thread, action.frames);
+ }
+
+ // Validate actions specific to a given frame while being paused.
+ // This will throw if we resumed or paused in another location.
+ // But compared to selected frame, this would not throw if we moved to another frame in the call stack.
+ // This ends up being similar to "pause location" case, but with different arguments.
+ if ("frame" in action) {
+ validateFrame(getState(), action.frame);
+ }
+
+ 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;
+ };
+}
diff --git a/devtools/client/debugger/src/actions/utils/moz.build b/devtools/client/debugger/src/actions/utils/moz.build
new file mode 100644
index 0000000000..08a43a218c
--- /dev/null
+++ b/devtools/client/debugger/src/actions/utils/moz.build
@@ -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 += [
+ "middleware",
+]
+
+CompiledModules(
+ "create-store.js",
+)