зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1746824 - Added JsonML Rep. r=nchevobbe,devtools-backward-compat-reviewers
This Rep hooks into the default display of objects and outputs a JsonML (http://www.jsonml.org) representation of them if custom formatters are enabled and a custom formatter is defined for them. It handles headers and bodies of custom formatters. Bodies are handled asynchronously from their corresponding headers. Because custom formatters allow a website to provide CSS styles to be used for formatting an object within the DevTools, those styles are filtered first to avoid breaking their layout. It is currently only applied to the output of the Webconsole and within the variable value popup and the watch expressions within the Debugger. *** Differential Revision: https://phabricator.services.mozilla.com/D140119
This commit is contained in:
Родитель
366719c66e
Коммит
6da32bdae4
|
@ -71,6 +71,10 @@ export class Popup extends Component {
|
|||
return 250;
|
||||
};
|
||||
|
||||
createElement(element) {
|
||||
return document.createElement(element);
|
||||
}
|
||||
|
||||
renderFunctionPreview() {
|
||||
const {
|
||||
cx,
|
||||
|
@ -127,10 +131,12 @@ export class Popup extends Component {
|
|||
disableWrap={true}
|
||||
focusable={false}
|
||||
openLink={openLink}
|
||||
createElement={this.createElement}
|
||||
onDOMNodeClick={grip => openElementInInspector(grip)}
|
||||
onInspectIconClick={grip => openElementInInspector(grip)}
|
||||
onDOMNodeMouseOver={grip => highlightDomElement(grip)}
|
||||
onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
|
||||
mayUseCustomFormatter={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -142,6 +142,10 @@ class Expressions extends Component {
|
|||
this.props.clearExpressionError();
|
||||
};
|
||||
|
||||
createElement = element => {
|
||||
return document.createElement(element);
|
||||
};
|
||||
|
||||
onFocus = () => {
|
||||
this.setState({ focused: true });
|
||||
};
|
||||
|
@ -222,6 +226,7 @@ class Expressions extends Component {
|
|||
autoExpandDepth={0}
|
||||
disableWrap={true}
|
||||
openLink={openLink}
|
||||
createElement={this.createElement}
|
||||
onDoubleClick={(items, { depth }) => {
|
||||
if (depth === 0) {
|
||||
this.editExpression(expression, index);
|
||||
|
@ -232,6 +237,7 @@ class Expressions extends Component {
|
|||
onDOMNodeMouseOver={grip => highlightDomElement(grip)}
|
||||
onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
|
||||
shouldRenderTooltip={true}
|
||||
mayUseCustomFormatter={true}
|
||||
/>
|
||||
<div className="expression-container__close-btn">
|
||||
<CloseButton
|
||||
|
|
|
@ -15,7 +15,9 @@ exports[`Expressions should always have unique keys 1`] = `
|
|||
>
|
||||
<Component
|
||||
autoExpandDepth={0}
|
||||
createElement={[Function]}
|
||||
disableWrap={true}
|
||||
mayUseCustomFormatter={true}
|
||||
onDOMNodeClick={[Function]}
|
||||
onDOMNodeMouseOut={[Function]}
|
||||
onDOMNodeMouseOver={[Function]}
|
||||
|
@ -58,7 +60,9 @@ exports[`Expressions should always have unique keys 1`] = `
|
|||
>
|
||||
<Component
|
||||
autoExpandDepth={0}
|
||||
createElement={[Function]}
|
||||
disableWrap={true}
|
||||
mayUseCustomFormatter={true}
|
||||
onDOMNodeClick={[Function]}
|
||||
onDOMNodeMouseOut={[Function]}
|
||||
onDOMNodeMouseOver={[Function]}
|
||||
|
@ -110,7 +114,9 @@ exports[`Expressions should render 1`] = `
|
|||
>
|
||||
<Component
|
||||
autoExpandDepth={0}
|
||||
createElement={[Function]}
|
||||
disableWrap={true}
|
||||
mayUseCustomFormatter={true}
|
||||
onDOMNodeClick={[Function]}
|
||||
onDOMNodeMouseOut={[Function]}
|
||||
onDOMNodeMouseOver={[Function]}
|
||||
|
@ -153,7 +159,9 @@ exports[`Expressions should render 1`] = `
|
|||
>
|
||||
<Component
|
||||
autoExpandDepth={0}
|
||||
createElement={[Function]}
|
||||
disableWrap={true}
|
||||
mayUseCustomFormatter={true}
|
||||
onDOMNodeClick={[Function]}
|
||||
onDOMNodeMouseOut={[Function]}
|
||||
onDOMNodeMouseOver={[Function]}
|
||||
|
|
|
@ -202,7 +202,11 @@ class ObjectInspector extends Component {
|
|||
}
|
||||
|
||||
isNodeExpandable(item) {
|
||||
if (nodeIsPrimitive(item)) {
|
||||
if (
|
||||
nodeIsPrimitive(item) ||
|
||||
(Array.isArray(item.contents?.value?.header) &&
|
||||
!item.contents?.value?.hasBody)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -101,6 +101,10 @@ async function getProxySlots(objectFront) {
|
|||
return objectFront.getProxySlots();
|
||||
}
|
||||
|
||||
async function getCustomFormatterBody(objectFront) {
|
||||
return objectFront.customFormatterBody();
|
||||
}
|
||||
|
||||
function iteratorSlice(iterator, start, end) {
|
||||
start = start || 0;
|
||||
const count = end ? end - start + 1 : iterator.count;
|
||||
|
@ -121,4 +125,5 @@ module.exports = {
|
|||
getFullText,
|
||||
getPromiseState,
|
||||
getProxySlots,
|
||||
getCustomFormatterBody,
|
||||
};
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
const client = require("devtools/client/shared/components/object-inspector/utils/client");
|
||||
const loadProperties = require("devtools/client/shared/components/object-inspector/utils/load-properties");
|
||||
const node = require("devtools/client/shared/components/object-inspector/utils/node");
|
||||
const { nodeIsError, nodeIsPrimitive } = node;
|
||||
const { nodeIsError, nodeIsPrimitive, nodeHasCustomFormatter, nodeHasCustomFormattedBody } = node;
|
||||
const selection = require("devtools/client/shared/components/object-inspector/utils/selection");
|
||||
|
||||
const { MODE } = require("devtools/client/shared/components/reps/reps/constants");
|
||||
const {
|
||||
MODE,
|
||||
} = require("devtools/client/shared/components/reps/reps/constants");
|
||||
const {
|
||||
REPS: { Rep, Grip },
|
||||
} = require("devtools/client/shared/components/reps/reps/rep");
|
||||
|
@ -20,9 +22,10 @@ function shouldRenderRootsInReps(roots, props = {}) {
|
|||
|
||||
const root = roots[0];
|
||||
const name = root && root.name;
|
||||
|
||||
return (
|
||||
(name === null || typeof name === "undefined") &&
|
||||
(nodeIsPrimitive(root) ||
|
||||
(nodeIsPrimitive(root) || (nodeHasCustomFormatter(root) && !nodeHasCustomFormattedBody(root)) ||
|
||||
(nodeIsError(root) && props?.customFormat === true))
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const {
|
|||
getFullText,
|
||||
getPromiseState,
|
||||
getProxySlots,
|
||||
getCustomFormatterBody,
|
||||
} = require("devtools/client/shared/components/object-inspector/utils/client");
|
||||
|
||||
const {
|
||||
|
@ -20,6 +21,8 @@ const {
|
|||
getFront,
|
||||
getValue,
|
||||
nodeHasAccessors,
|
||||
nodeHasCustomFormatter,
|
||||
nodeHasCustomFormattedBody,
|
||||
nodeHasProperties,
|
||||
nodeIsBucket,
|
||||
nodeIsDefaultProperties,
|
||||
|
@ -95,6 +98,10 @@ function loadItemProperties(item, client, loadedProperties, threadActorID) {
|
|||
promises.push(getProxySlots(getObjectFront()));
|
||||
}
|
||||
|
||||
if (shouldLoadCustomFormatterBody(item, loadedProperties)) {
|
||||
promises.push(getCustomFormatterBody(getObjectFront()));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(mergeResponses);
|
||||
}
|
||||
|
||||
|
@ -130,6 +137,10 @@ function mergeResponses(responses) {
|
|||
data.proxyTarget = response.proxyTarget;
|
||||
data.proxyHandler = response.proxyHandler;
|
||||
}
|
||||
|
||||
if (response.customFormatterBody) {
|
||||
data.customFormatterBody = response.customFormatterBody;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -146,6 +157,7 @@ function shouldLoadItemIndexedProperties(item, loadedProperties = new Map()) {
|
|||
!nodeIsProxy(item) &&
|
||||
!nodeNeedsNumericalBuckets(item) &&
|
||||
!nodeIsEntries(getClosestNonBucketNode(item)) &&
|
||||
!nodeHasCustomFormatter(item) &&
|
||||
// The data is loaded when expanding the window node.
|
||||
!nodeIsDefaultProperties(item)
|
||||
);
|
||||
|
@ -165,6 +177,7 @@ function shouldLoadItemNonIndexedProperties(
|
|||
!nodeIsProxy(item) &&
|
||||
!nodeIsEntries(getClosestNonBucketNode(item)) &&
|
||||
!nodeIsBucket(item) &&
|
||||
!nodeHasCustomFormatter(item) &&
|
||||
// The data is loaded when expanding the window node.
|
||||
!nodeIsDefaultProperties(item)
|
||||
);
|
||||
|
@ -178,6 +191,7 @@ function shouldLoadItemEntries(item, loadedProperties = new Map()) {
|
|||
value &&
|
||||
nodeIsEntries(getClosestNonBucketNode(item)) &&
|
||||
!loadedProperties.has(item.path) &&
|
||||
!nodeHasCustomFormatter(item) &&
|
||||
!nodeNeedsNumericalBuckets(item)
|
||||
);
|
||||
}
|
||||
|
@ -195,7 +209,8 @@ function shouldLoadItemPrototype(item, loadedProperties = new Map()) {
|
|||
!nodeHasAccessors(item) &&
|
||||
!nodeIsPrimitive(item) &&
|
||||
!nodeIsLongString(item) &&
|
||||
!nodeIsProxy(item)
|
||||
!nodeIsProxy(item) &&
|
||||
!nodeHasCustomFormatter(item)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -212,7 +227,8 @@ function shouldLoadItemSymbols(item, loadedProperties = new Map()) {
|
|||
!nodeHasAccessors(item) &&
|
||||
!nodeIsPrimitive(item) &&
|
||||
!nodeIsLongString(item) &&
|
||||
!nodeIsProxy(item)
|
||||
!nodeIsProxy(item) &&
|
||||
!nodeHasCustomFormatter(item)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -230,20 +246,25 @@ function shouldLoadItemPrivateProperties(item, loadedProperties = new Map()) {
|
|||
!nodeHasAccessors(item) &&
|
||||
!nodeIsPrimitive(item) &&
|
||||
!nodeIsLongString(item) &&
|
||||
!nodeIsProxy(item)
|
||||
!nodeIsProxy(item) &&
|
||||
!nodeHasCustomFormatter(item)
|
||||
);
|
||||
}
|
||||
|
||||
function shouldLoadItemFullText(item, loadedProperties = new Map()) {
|
||||
return !loadedProperties.has(item.path) && nodeIsLongString(item);
|
||||
return !loadedProperties.has(item.path) && nodeIsLongString(item) && !nodeHasCustomFormatter(item);
|
||||
}
|
||||
|
||||
function shouldLoadItemPromiseState(item, loadedProperties = new Map()) {
|
||||
return !loadedProperties.has(item.path) && nodeIsPromise(item);
|
||||
return !loadedProperties.has(item.path) && nodeIsPromise(item) && !nodeHasCustomFormatter(item);
|
||||
}
|
||||
|
||||
function shouldLoadItemProxySlots(item, loadedProperties = new Map()) {
|
||||
return !loadedProperties.has(item.path) && nodeIsProxy(item);
|
||||
return !loadedProperties.has(item.path) && nodeIsProxy(item) && !nodeHasCustomFormatter(item);
|
||||
}
|
||||
|
||||
function shouldLoadCustomFormatterBody(item, loadedProperties = new Map()) {
|
||||
return !loadedProperties.has(item.path) && nodeHasCustomFormattedBody(item)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -257,4 +278,5 @@ module.exports = {
|
|||
shouldLoadItemFullText,
|
||||
shouldLoadItemPromiseState,
|
||||
shouldLoadItemProxySlots,
|
||||
shouldLoadCustomFormatterBody,
|
||||
};
|
||||
|
|
|
@ -23,6 +23,7 @@ const NODE_TYPES = {
|
|||
ENTRIES: Symbol("<entries>"),
|
||||
GET: Symbol("<get>"),
|
||||
GRIP: Symbol("GRIP"),
|
||||
JSONML: Symbol("JsonML"),
|
||||
MAP_ENTRY_KEY: Symbol("<key>"),
|
||||
MAP_ENTRY_VALUE: Symbol("<value>"),
|
||||
PROMISE_REASON: Symbol("<reason>"),
|
||||
|
@ -100,6 +101,14 @@ function nodeHasChildren(item) {
|
|||
return Array.isArray(item.contents);
|
||||
}
|
||||
|
||||
function nodeHasCustomFormatter(item) {
|
||||
return item?.contents?.value?.useCustomFormatter === true && Array.isArray(item?.contents?.value?.header);
|
||||
}
|
||||
|
||||
function nodeHasCustomFormattedBody(item) {
|
||||
return item?.contents?.value?.hasBody === true;
|
||||
}
|
||||
|
||||
function nodeHasValue(item) {
|
||||
return item && item.contents && item.contents.hasOwnProperty("value");
|
||||
}
|
||||
|
@ -362,6 +371,22 @@ function makeNodesForProxyProperties(loadedProps, item) {
|
|||
];
|
||||
}
|
||||
|
||||
function makeJsonMlNode(loadedProps, item) {
|
||||
return [
|
||||
createNode({
|
||||
parent: item,
|
||||
path: "body",
|
||||
contents: {
|
||||
value: {
|
||||
header: loadedProps.customFormatterBody,
|
||||
useCustomFormatter: true,
|
||||
},
|
||||
},
|
||||
type: NODE_TYPES.JSONML,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
function makeNodesForEntries(item) {
|
||||
const nodeName = "<entries>";
|
||||
|
||||
|
@ -819,6 +844,10 @@ function getChildren(options) {
|
|||
return children;
|
||||
};
|
||||
|
||||
if (nodeHasCustomFormattedBody(item) && hasLoadedProps) {
|
||||
return addToCache(makeJsonMlNode(loadedProps, item));
|
||||
}
|
||||
|
||||
// Nodes can either have children already, or be an object with
|
||||
// properties that we need to go and fetch.
|
||||
if (nodeHasChildren(item)) {
|
||||
|
@ -993,12 +1022,15 @@ module.exports = {
|
|||
getNonPrototypeParentGripValue,
|
||||
getNumericalPropertiesCount,
|
||||
getValue,
|
||||
makeJsonMlNode,
|
||||
makeNodesForEntries,
|
||||
makeNodesForPromiseProperties,
|
||||
makeNodesForProperties,
|
||||
makeNumericalBuckets,
|
||||
nodeHasAccessors,
|
||||
nodeHasChildren,
|
||||
nodeHasCustomFormattedBody,
|
||||
nodeHasCustomFormatter,
|
||||
nodeHasEntries,
|
||||
nodeHasProperties,
|
||||
nodeHasGetter,
|
||||
|
|
|
@ -149,6 +149,16 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* JsonML reps can be nested, though only the top-level rep needs layout
|
||||
* adjustments to align it with the toggle arrow and fit its width to its
|
||||
* contents. */
|
||||
:not(.objectBox-jsonml) > .objectBox-jsonml {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
width: fit-content;
|
||||
word-break: break-word;
|
||||
line-height: normal;
|
||||
}
|
||||
/******************************************************************************/
|
||||
|
||||
.objectBox-event,
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
/* 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";
|
||||
|
||||
// Make this available to both AMD and CJS environments
|
||||
define(function(require, exports, module) {
|
||||
// ReactJS
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
// Dependencies
|
||||
const { createElement } = require("devtools/client/shared/vendor/react");
|
||||
const {
|
||||
cleanupStyle,
|
||||
} = require("devtools/client/shared/components/reps/reps/rep-utils");
|
||||
|
||||
const {
|
||||
wrapRender,
|
||||
} = require("devtools/client/shared/components/reps/reps/rep-utils");
|
||||
|
||||
const ALLOWED_TAGS = new Set([
|
||||
"span",
|
||||
"div",
|
||||
"ol",
|
||||
"ul",
|
||||
"li",
|
||||
"table",
|
||||
"tr",
|
||||
"td",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Renders null value
|
||||
*/
|
||||
JsonMl.PropTypes = {
|
||||
object: PropTypes.object.isRequired,
|
||||
createElement: PropTypes.func,
|
||||
};
|
||||
|
||||
function JsonMl(props) {
|
||||
// The second item of the array can either be an object holding the attributes
|
||||
// for the element or the first child element. Therefore, all array items after the
|
||||
// first one are fetched together and split afterwards if needed.
|
||||
let [tagName, ...attributesAndChildren] = props.object.header;
|
||||
|
||||
if (!ALLOWED_TAGS.has(tagName)) {
|
||||
tagName = "div";
|
||||
}
|
||||
|
||||
const attributes = attributesAndChildren[0];
|
||||
const hasAttributes =
|
||||
Object(attributes) === attributes && !Array.isArray(attributes);
|
||||
const style =
|
||||
hasAttributes && attributes?.style && props.createElement
|
||||
? cleanupStyle(attributes.style, props.createElement)
|
||||
: null;
|
||||
const children = attributesAndChildren;
|
||||
if (hasAttributes) {
|
||||
children.shift();
|
||||
}
|
||||
|
||||
const childElements = [];
|
||||
if (Array.isArray(children)) {
|
||||
children.forEach((child, index) => {
|
||||
childElements.push(
|
||||
// If the child is an array, it should be a JsonML item, so use this function to
|
||||
// render them, otherwise it should be a string and we can directly render it.
|
||||
Array.isArray(child)
|
||||
? JsonMl({ ...props, object: { header: child, index } })
|
||||
: child
|
||||
);
|
||||
});
|
||||
} else {
|
||||
childElements.push(children);
|
||||
}
|
||||
|
||||
return createElement(
|
||||
tagName,
|
||||
{
|
||||
className: "objectBox objectBox-jsonml",
|
||||
key: `jsonml-${tagName}-${props.object.index ?? 0}`,
|
||||
style,
|
||||
},
|
||||
childElements
|
||||
);
|
||||
}
|
||||
|
||||
function supportsObject(grip) {
|
||||
return grip?.useCustomFormatter === true && Array.isArray(grip?.header);
|
||||
}
|
||||
|
||||
// Exports from this module
|
||||
|
||||
module.exports = {
|
||||
rep: wrapRender(JsonMl),
|
||||
supportsObject,
|
||||
};
|
||||
});
|
|
@ -24,6 +24,7 @@ DevToolsModules(
|
|||
"grip-map.js",
|
||||
"grip.js",
|
||||
"infinity.js",
|
||||
"jsonml.js",
|
||||
"nan.js",
|
||||
"null.js",
|
||||
"number.js",
|
||||
|
|
|
@ -469,6 +469,58 @@ define(function(require, exports, module) {
|
|||
ELLIPSIS
|
||||
);
|
||||
|
||||
/**
|
||||
* Removes any unallowed CSS properties from a string of CSS declarations
|
||||
*
|
||||
* @param {String} userProvidedStyle CSS declarations
|
||||
* @param {Function} createElement Method to create a dummy element the styles get applied to
|
||||
* @returns {Object} Filtered CSS properties as JavaScript object in camelCase notation
|
||||
*/
|
||||
function cleanupStyle(userProvidedStyle, createElement) {
|
||||
// Regular expression that matches the allowed CSS property names.
|
||||
const allowedStylesRegex = new RegExp(
|
||||
"^(?:-moz-)?(?:background|border|box|clear|color|cursor|display|float|font|line|" +
|
||||
"margin|padding|text|transition|outline|white-space|word|writing|" +
|
||||
"(?:min-|max-)?width|(?:min-|max-)?height)"
|
||||
);
|
||||
|
||||
// Regular expression that matches the forbidden CSS property values.
|
||||
const forbiddenValuesRegexs = [
|
||||
// -moz-element()
|
||||
/\b((?:-moz-)?element)[\s('"]+/gi,
|
||||
|
||||
// various URL protocols
|
||||
/['"(]*(?:chrome|resource|about|app|https?|ftp|file):+\/*/gi,
|
||||
];
|
||||
|
||||
// Use a dummy element to parse the style string.
|
||||
const dummy = createElement("div");
|
||||
dummy.style = userProvidedStyle;
|
||||
|
||||
// Return a style object as expected by React DOM components, e.g.
|
||||
// {color: "red"}
|
||||
// without forbidden properties and values.
|
||||
return Array.from(dummy.style)
|
||||
.filter(name => {
|
||||
return (
|
||||
allowedStylesRegex.test(name) &&
|
||||
!forbiddenValuesRegexs.some(regex => regex.test(dummy.style[name]))
|
||||
);
|
||||
})
|
||||
.reduce((object, name) => {
|
||||
// React requires CSS properties to be provided in JavaScript form, i.e. camelCased.
|
||||
const jsName = name.replace(/-([a-z])/g, (_, char) =>
|
||||
char.toUpperCase()
|
||||
);
|
||||
return Object.assign(
|
||||
{
|
||||
[jsName]: dummy.style.getPropertyValue(name),
|
||||
},
|
||||
object
|
||||
);
|
||||
}, {});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
interleave,
|
||||
isURL,
|
||||
|
@ -490,5 +542,6 @@ define(function(require, exports, module) {
|
|||
ELLIPSIS,
|
||||
uneatLastUrlCharsRegex,
|
||||
urlRegex,
|
||||
cleanupStyle,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -89,10 +89,73 @@ define(function(require, exports, module) {
|
|||
*/
|
||||
const Rep = function(props) {
|
||||
const { object, defaultRep } = props;
|
||||
const rep = getRep(object, defaultRep, props.noGrip);
|
||||
const rep = getRep(
|
||||
object,
|
||||
defaultRep,
|
||||
props.noGrip,
|
||||
props.mayUseCustomFormatter
|
||||
);
|
||||
return rep(props);
|
||||
};
|
||||
|
||||
const exportedReps = {
|
||||
Accessible,
|
||||
Accessor,
|
||||
ArrayRep,
|
||||
Attribute,
|
||||
BigInt,
|
||||
CommentNode,
|
||||
DateTime,
|
||||
Document,
|
||||
DocumentType,
|
||||
ElementNode,
|
||||
ErrorRep,
|
||||
Event,
|
||||
Func,
|
||||
Grip,
|
||||
GripArray,
|
||||
GripMap,
|
||||
GripMapEntry,
|
||||
InfinityRep,
|
||||
NaNRep,
|
||||
Null,
|
||||
Number,
|
||||
Obj,
|
||||
ObjectWithText,
|
||||
ObjectWithURL,
|
||||
PromiseRep,
|
||||
RegExp,
|
||||
Rep,
|
||||
StringRep,
|
||||
StyleSheet,
|
||||
SymbolRep,
|
||||
TextNode,
|
||||
Undefined,
|
||||
Window,
|
||||
};
|
||||
|
||||
// Custom Formatters
|
||||
// ToDo: This preference can be removed once the custom formatters feature is stable enough
|
||||
const Services = require("Services");
|
||||
// Services.prefs isn't available in jsonviewer. It doesn't matter as we don't want to use
|
||||
// custom formatters there
|
||||
if (Services?.prefs) {
|
||||
const customFormattersExperimentallyEnabled = Services.prefs.getBoolPref(
|
||||
"devtools.custom-formatters",
|
||||
false
|
||||
);
|
||||
|
||||
const useCustomFormatters =
|
||||
customFormattersExperimentallyEnabled &&
|
||||
Services.prefs.getBoolPref("devtools.custom-formatters.enabled", false);
|
||||
|
||||
if (useCustomFormatters) {
|
||||
const JsonMl = require("devtools/client/shared/components/reps/reps/jsonml");
|
||||
reps.unshift(JsonMl);
|
||||
exportedReps.JsonMl = JsonMl;
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
/**
|
||||
|
@ -108,10 +171,22 @@ define(function(require, exports, module) {
|
|||
*
|
||||
* @param noGrip {Boolean} If true, will only check reps not made for remote
|
||||
* objects.
|
||||
*
|
||||
* @param mayUseCustomFormatter {Boolean} If true, custom formatters are
|
||||
* allowed to be used as rep.
|
||||
*/
|
||||
function getRep(object, defaultRep = Grip, noGrip = false) {
|
||||
function getRep(
|
||||
object,
|
||||
defaultRep = Grip,
|
||||
noGrip = false,
|
||||
mayUseCustomFormatter = false
|
||||
) {
|
||||
const repsList = noGrip ? noGripReps : reps;
|
||||
for (const rep of repsList) {
|
||||
if (rep === exportedReps.JsonMl && !mayUseCustomFormatter) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// supportsObject could return weight (not only true/false
|
||||
// but a number), which would allow to priorities templates and
|
||||
|
@ -129,41 +204,7 @@ define(function(require, exports, module) {
|
|||
|
||||
module.exports = {
|
||||
Rep,
|
||||
REPS: {
|
||||
Accessible,
|
||||
Accessor,
|
||||
ArrayRep,
|
||||
Attribute,
|
||||
BigInt,
|
||||
CommentNode,
|
||||
DateTime,
|
||||
Document,
|
||||
DocumentType,
|
||||
ElementNode,
|
||||
ErrorRep,
|
||||
Event,
|
||||
Func,
|
||||
Grip,
|
||||
GripArray,
|
||||
GripMap,
|
||||
GripMapEntry,
|
||||
InfinityRep,
|
||||
NaNRep,
|
||||
Null,
|
||||
Number,
|
||||
Obj,
|
||||
ObjectWithText,
|
||||
ObjectWithURL,
|
||||
PromiseRep,
|
||||
RegExp,
|
||||
Rep,
|
||||
StringRep,
|
||||
StyleSheet,
|
||||
SymbolRep,
|
||||
TextNode,
|
||||
Undefined,
|
||||
Window,
|
||||
},
|
||||
REPS: exportedReps,
|
||||
// Exporting for tests
|
||||
getRep,
|
||||
};
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
background-color: var(--theme-selection-background);
|
||||
}
|
||||
|
||||
.treeTable .treeRow.selected *,
|
||||
.treeTable .treeRow.selected :where(:not(.objectBox-jsonml)),
|
||||
.treeTable .treeRow.selected .treeLabelCell::after {
|
||||
color: var(--theme-selection-color);
|
||||
fill: currentColor;
|
||||
|
|
|
@ -10,6 +10,9 @@ const {
|
|||
MESSAGE_TYPE,
|
||||
JSTERM_COMMANDS,
|
||||
} = require("devtools/client/webconsole/constants");
|
||||
const {
|
||||
cleanupStyle,
|
||||
} = require("devtools/client/shared/components/reps/reps/rep-utils");
|
||||
const {
|
||||
getObjectInspector,
|
||||
} = require("devtools/client/webconsole/utils/object-inspector");
|
||||
|
@ -101,47 +104,6 @@ function GripMessageBody(props) {
|
|||
return getObjectInspector(grip, serviceContainer, objectInspectorProps);
|
||||
}
|
||||
|
||||
// Regular expression that matches the allowed CSS property names.
|
||||
const allowedStylesRegex = new RegExp(
|
||||
"^(?:-moz-)?(?:background|border|box|clear|color|cursor|display|float|font|line|" +
|
||||
"margin|padding|text|transition|outline|white-space|word|writing|" +
|
||||
"(?:min-|max-)?width|(?:min-|max-)?height)"
|
||||
);
|
||||
|
||||
// Regular expression that matches the forbidden CSS property values.
|
||||
const forbiddenValuesRegexs = [
|
||||
// -moz-element()
|
||||
/\b((?:-moz-)?element)[\s('"]+/gi,
|
||||
|
||||
// various URL protocols
|
||||
/['"(]*(?:chrome|resource|about|app|https?|ftp|file):+\/*/gi,
|
||||
];
|
||||
|
||||
function cleanupStyle(userProvidedStyle, createElement) {
|
||||
// Use a dummy element to parse the style string.
|
||||
const dummy = createElement("div");
|
||||
dummy.style = userProvidedStyle;
|
||||
|
||||
// Return a style object as expected by React DOM components, e.g.
|
||||
// {color: "red"}
|
||||
// without forbidden properties and values.
|
||||
return Array.from(dummy.style)
|
||||
.filter(name => {
|
||||
return (
|
||||
allowedStylesRegex.test(name) &&
|
||||
!forbiddenValuesRegexs.some(regex => regex.test(dummy.style[name]))
|
||||
);
|
||||
})
|
||||
.reduce((object, name) => {
|
||||
return Object.assign(
|
||||
{
|
||||
[name]: dummy.style[name],
|
||||
},
|
||||
object
|
||||
);
|
||||
}, {});
|
||||
}
|
||||
|
||||
function shouldAutoExpandObjectInspector(props) {
|
||||
const { helperType, type } = props;
|
||||
|
||||
|
|
|
@ -119,6 +119,8 @@ function getObjectInspector(
|
|||
onDOMNodeMouseOut,
|
||||
onInspectIconClick,
|
||||
defaultRep: REPS.Grip,
|
||||
createElement: serviceContainer?.createElement,
|
||||
mayUseCustomFormatter: true,
|
||||
...override,
|
||||
};
|
||||
|
||||
|
|
|
@ -753,6 +753,17 @@ const proto = {
|
|||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a protocol request to get the custom formatter body for an object
|
||||
*/
|
||||
customFormatterBody: function() {
|
||||
// ToDo: The body is currently set to the default value `null`. It needs to be
|
||||
// generated by parsing the custom formatters from the page. (see bug 1734840)
|
||||
return {
|
||||
customFormatterBody: null,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Release the actor, when it isn't needed anymore.
|
||||
* Protocol.js uses this release method to call the destroy method.
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"use strict";
|
||||
|
||||
const { Cu, Ci } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { DevToolsServer } = require("devtools/server/devtools-server");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
loader.lazyRequireGetter(
|
||||
|
@ -519,6 +520,21 @@ function GenericObject(objectActor, grip, rawObj, className) {
|
|||
}
|
||||
}
|
||||
|
||||
// ToDo: This preference can be removed once the custom formatters feature is stable enough
|
||||
const customFormattersExperimentallyEnabled = isWorker
|
||||
? false
|
||||
: Services.prefs.getBoolPref("devtools.custom-formatters");
|
||||
|
||||
if (customFormattersExperimentallyEnabled) {
|
||||
const useCustomFormatters = Services.prefs.getBoolPref(
|
||||
"devtools.custom-formatters.enabled"
|
||||
);
|
||||
|
||||
grip.useCustomFormatter = useCustomFormatters;
|
||||
grip.header = null;
|
||||
grip.hasBody = false;
|
||||
}
|
||||
|
||||
for (const name of names) {
|
||||
if (specialStringBehavior && /^[0-9]+$/.test(name)) {
|
||||
const num = parseInt(name, 10);
|
||||
|
|
|
@ -89,6 +89,10 @@ types.addDictType("object.proxySlots", {
|
|||
proxyHandler: "object.descriptor",
|
||||
});
|
||||
|
||||
types.addDictType("object.customFormatterBody", {
|
||||
customFormatterBody: "json",
|
||||
});
|
||||
|
||||
const objectSpec = generateActorSpec({
|
||||
typeName: "obj",
|
||||
methods: {
|
||||
|
@ -176,6 +180,10 @@ const objectSpec = generateActorSpec({
|
|||
request: {},
|
||||
response: RetVal("object.proxySlots"),
|
||||
},
|
||||
customFormatterBody: {
|
||||
request: {},
|
||||
response: RetVal("object.customFormatterBody"),
|
||||
},
|
||||
addWatchpoint: {
|
||||
request: {
|
||||
property: Arg(0, "string"),
|
||||
|
|
Загрузка…
Ссылка в новой задаче