summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/components/object-inspector/actions.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/components/object-inspector/actions.js')
-rw-r--r--devtools/client/shared/components/object-inspector/actions.js225
1 files changed, 225 insertions, 0 deletions
diff --git a/devtools/client/shared/components/object-inspector/actions.js b/devtools/client/shared/components/object-inspector/actions.js
new file mode 100644
index 0000000000..370f1b161a
--- /dev/null
+++ b/devtools/client/shared/components/object-inspector/actions.js
@@ -0,0 +1,225 @@
+/* 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/>. */
+
+const { loadItemProperties } = require("resource://devtools/client/shared/components/object-inspector/utils/load-properties.js");
+const {
+ getPathExpression,
+ getParentFront,
+ getParentGripValue,
+ getValue,
+ nodeIsBucket,
+ getFront,
+} = require("resource://devtools/client/shared/components/object-inspector/utils/node.js");
+const { getLoadedProperties, getWatchpoints } = require("resource://devtools/client/shared/components/object-inspector/reducer.js");
+
+/**
+ * This action is responsible for expanding a given node, which also means that
+ * it will call the action responsible to fetch properties.
+ */
+function nodeExpand(node, actor) {
+ return async ({ dispatch }) => {
+ dispatch({ type: "NODE_EXPAND", data: { node } });
+ dispatch(nodeLoadProperties(node, actor));
+ };
+}
+
+function nodeCollapse(node) {
+ return {
+ type: "NODE_COLLAPSE",
+ data: { node },
+ };
+}
+
+/*
+ * This action checks if we need to fetch properties, entries, prototype and
+ * symbols for a given node. If we do, it will call the appropriate ObjectFront
+ * functions.
+ */
+function nodeLoadProperties(node, actor) {
+ return async ({ dispatch, client, getState }) => {
+ const state = getState();
+ const loadedProperties = getLoadedProperties(state);
+ if (loadedProperties.has(node.path)) {
+ return;
+ }
+
+ try {
+ const properties = await loadItemProperties(
+ node,
+ client,
+ loadedProperties
+ );
+
+ // If the client does not have a releaseActor function, it means the actors are
+ // handled directly by the consumer, so we don't need to track them.
+ if (!client || !client.releaseActor) {
+ actor = null;
+ }
+
+ dispatch(nodePropertiesLoaded(node, actor, properties));
+ } catch (e) {
+ console.error(e);
+ }
+ };
+}
+
+function nodePropertiesLoaded(node, actor, properties) {
+ return {
+ type: "NODE_PROPERTIES_LOADED",
+ data: { node, actor, properties },
+ };
+}
+
+/*
+ * This action adds a property watchpoint to an object
+ */
+function addWatchpoint(item, watchpoint) {
+ return async function({ dispatch, client }) {
+ const { parent, name } = item;
+ let object = getValue(parent);
+
+ if (nodeIsBucket(parent)) {
+ object = getValue(parent.parent);
+ }
+
+ if (!object) {
+ return;
+ }
+
+ const path = parent.path;
+ const property = name;
+ const label = getPathExpression(item);
+ const actor = object.actor;
+
+ await client.addWatchpoint(object, property, label, watchpoint);
+
+ dispatch({
+ type: "SET_WATCHPOINT",
+ data: { path, watchpoint, property, actor },
+ });
+ };
+}
+
+/*
+ * This action removes a property watchpoint from an object
+ */
+function removeWatchpoint(item) {
+ return async function({ dispatch, client }) {
+ const { parent, name } = item;
+ let object = getValue(parent);
+
+ if (nodeIsBucket(parent)) {
+ object = getValue(parent.parent);
+ }
+
+ const property = name;
+ const path = parent.path;
+ const actor = object.actor;
+
+ await client.removeWatchpoint(object, property);
+
+ dispatch({
+ type: "REMOVE_WATCHPOINT",
+ data: { path, property, actor },
+ });
+ };
+}
+
+function getActorIDs(roots) {
+ if (!roots) {
+ return []
+ }
+
+ const actorIds = [];
+ for (const root of roots) {
+ const front = getFront(root);
+ if (front?.actorID) {
+ actorIds.push(front.actorID);
+ }
+ }
+
+ return actorIds;
+}
+
+function closeObjectInspector(roots) {
+ return ({ client }) => {
+ releaseActors(client, roots);
+ };
+}
+
+/*
+ * This action is dispatched when the `roots` prop, provided by a consumer of
+ * the ObjectInspector (inspector, console, …), is modified. It will clean the
+ * internal state properties (expandedPaths, loadedProperties, …) and release
+ * the actors consumed with the previous roots.
+ * It takes a props argument which reflects what is passed by the upper-level
+ * consumer.
+ */
+function rootsChanged(roots, oldRoots) {
+ return ({ dispatch, client }) => {
+ releaseActors(client, oldRoots, roots);
+ dispatch({
+ type: "ROOTS_CHANGED",
+ data: roots,
+ });
+ };
+}
+
+/**
+ * Release any actors we don't need anymore
+ *
+ * @param {Object} client: Object with a `releaseActor` method
+ * @param {Array} oldRoots: The roots in which we want to cleanup now-unused actors
+ * @param {Array} newRoots: The current roots (might have item that are also in oldRoots)
+ */
+async function releaseActors(client, oldRoots, newRoots = []) {
+ if (!client?.releaseActor ) {
+ return;
+ }
+
+ let actorIdsToRelease = getActorIDs(oldRoots);
+ if (newRoots.length) {
+ const newActorIds = getActorIDs(newRoots);
+ actorIdsToRelease = actorIdsToRelease.filter(id => !newActorIds.includes(id));
+ }
+
+ if (!actorIdsToRelease.length) {
+ return;
+ }
+ await Promise.all(actorIdsToRelease.map(client.releaseActor));
+}
+
+function invokeGetter(node, receiverId) {
+ return async ({ dispatch, client, getState }) => {
+ try {
+ const objectFront =
+ getParentFront(node) ||
+ client.createObjectFront(getParentGripValue(node));
+ const getterName = node.propertyName || node.name;
+
+ const result = await objectFront.getPropertyValue(getterName, receiverId);
+ dispatch({
+ type: "GETTER_INVOKED",
+ data: {
+ node,
+ result,
+ },
+ });
+ } catch (e) {
+ console.error(e);
+ }
+ };
+}
+
+module.exports = {
+ closeObjectInspector,
+ invokeGetter,
+ nodeExpand,
+ nodeCollapse,
+ nodeLoadProperties,
+ nodePropertiesLoaded,
+ rootsChanged,
+ addWatchpoint,
+ removeWatchpoint,
+};