228 lines
5.7 KiB
JavaScript
228 lines
5.7 KiB
JavaScript
/* 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 } = 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);
|
|
dispatch(nodeCollapse(node));
|
|
}
|
|
};
|
|
}
|
|
|
|
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, autoReleaseObjectActors) {
|
|
return ({ dispatch, client }) => {
|
|
if (autoReleaseObjectActors) {
|
|
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,
|
|
};
|