devtools-reps
Advanced tools
Comparing version 0.22.0 to 0.23.0
{ | ||
"name": "devtools-reps", | ||
"version": "0.22.0", | ||
"version": "0.23.0", | ||
"description": "Devtools Reps", | ||
@@ -29,3 +29,3 @@ "main": "src/index.js", | ||
"classnames": "^2.2.5", | ||
"devtools-components": "^0.4.1", | ||
"devtools-components": "^0.6.0", | ||
"lodash": "^4.17.2", | ||
@@ -48,3 +48,3 @@ "prop-types": "^15.6.0", | ||
"devtools-license-check": "^0.5.1", | ||
"devtools-modules": "^0.0.34", | ||
"devtools-modules": "^0.0.35", | ||
"enzyme": "^3.3.0", | ||
@@ -51,0 +51,0 @@ "enzyme-adapter-react-16": "^1.1.1", |
@@ -56,2 +56,8 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
function createLongStringClient(grip) { | ||
return function ({dispatch, client}) { | ||
return client.getLongStringClient(grip); | ||
}; | ||
} | ||
function releaseActor(actor) { | ||
@@ -69,3 +75,4 @@ return function ({dispatch, client}) { | ||
createObjectClient, | ||
createLongStringClient, | ||
releaseActor, | ||
}; |
@@ -8,5 +8,5 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
module.exports = Object.assign({}, | ||
expressions, | ||
input, | ||
); | ||
module.exports = { | ||
...expressions, | ||
...input, | ||
}; |
@@ -34,2 +34,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
createObjectClient: PropTypes.func.isRequired, | ||
createLongStringClient: PropTypes.func.isRequired, | ||
releaseActor: PropTypes.func.isRequired, | ||
@@ -59,2 +60,3 @@ }; | ||
createObjectClient, | ||
createLongStringClient, | ||
releaseActor, | ||
@@ -78,2 +80,3 @@ } = this.props; | ||
createObjectClient, | ||
createLongStringClient, | ||
releaseActor, | ||
@@ -80,0 +83,0 @@ }) |
@@ -20,2 +20,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
createObjectClient: PropTypes.func.isRequired, | ||
createLongStringClient: PropTypes.func.isRequired, | ||
releaseActor: PropTypes.func.isRequired, | ||
@@ -63,2 +64,3 @@ }; | ||
createObjectClient, | ||
createLongStringClient, | ||
releaseActor, | ||
@@ -83,5 +85,12 @@ } = this.props; | ||
createObjectClient, | ||
createLongStringClient, | ||
releaseActor, | ||
mode: MODE[modeKey], | ||
onInspectIconClick: nodeFront => console.log("inspectIcon click", nodeFront), | ||
disableFocus: false, | ||
// The following properties are optional function props called by the | ||
// objectInspector on some occasions. Here we pass dull functions that only | ||
// logs the parameters with which the callback was called. | ||
onCmdCtrlClick: (node, { depth, event, focused, expanded }) => | ||
console.log("CmdCtrlClick", {node, depth, event, focused, expanded}), | ||
onInspectIconClick: nodeFront => console.log("inspectIcon click", {nodeFront}), | ||
onViewSourceInDebugger: location => | ||
@@ -88,0 +97,0 @@ console.log("onViewSourceInDebugger", {location}), |
@@ -20,2 +20,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
createObjectClient: PropTypes.func.isRequired, | ||
createLongStringClient: PropTypes.func.isRequired, | ||
releaseActor: PropTypes.func.isRequired, | ||
@@ -31,2 +32,3 @@ }; | ||
createObjectClient, | ||
createLongStringClient, | ||
releaseActor, | ||
@@ -46,2 +48,3 @@ } = this.props; | ||
createObjectClient, | ||
createLongStringClient, | ||
releaseActor, | ||
@@ -48,0 +51,0 @@ }) |
@@ -6,2 +6,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
// globals window, document | ||
require("../reps/reps.css"); | ||
@@ -38,2 +39,6 @@ | ||
getLongStringClient: function (grip) { | ||
return connection.tabConnection.tabTarget.activeConsole.longString(grip); | ||
}, | ||
releaseActor: function (actor) { | ||
@@ -45,3 +50,3 @@ return connection.tabConnection.debuggerClient.release(actor); | ||
let store = configureStore({ | ||
makeThunkArgs: (args, state) => Object.assign({}, args, { client }), | ||
makeThunkArgs: (args, state) => ({ ...args, client }), | ||
client, | ||
@@ -48,0 +53,0 @@ }); |
@@ -18,3 +18,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
return createStore( | ||
combineReducers(Object.assign({client}, reducers)), | ||
combineReducers({ client, ...reducers }), | ||
applyMiddleware( | ||
@@ -21,0 +21,0 @@ thunk(options.makeThunkArgs), |
@@ -8,7 +8,8 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
import type { | ||
CreateLongStringClient, | ||
CreateObjectClient, | ||
GripProperties, | ||
LoadedProperties, | ||
Node, | ||
ObjectClient, | ||
RdpGrip, | ||
Props, | ||
ReduxAction, | ||
@@ -36,3 +37,4 @@ } from "./types"; | ||
loadedProperties : LoadedProperties, | ||
createObjectClient : (RdpGrip) => ObjectClient | ||
createObjectClient : CreateObjectClient, | ||
createLongStringClient : CreateLongStringClient | ||
) { | ||
@@ -46,3 +48,4 @@ return async ({dispatch} : ThunkArg) => { | ||
if (!loadedProperties.has(node.path)) { | ||
dispatch(nodeLoadProperties(node, actor, loadedProperties, createObjectClient)); | ||
dispatch(nodeLoadProperties(node, actor, loadedProperties, createObjectClient, | ||
createLongStringClient)); | ||
} | ||
@@ -73,3 +76,4 @@ }; | ||
loadedProperties : LoadedProperties, | ||
createObjectClient : (RdpGrip) => ObjectClient | ||
createObjectClient : CreateObjectClient, | ||
createLongStringClient : CreateLongStringClient | ||
) { | ||
@@ -79,3 +83,4 @@ return async ({dispatch} : ThunkArg) => { | ||
const properties = | ||
await loadItemProperties(item, createObjectClient, loadedProperties); | ||
await loadItemProperties(item, createObjectClient, createLongStringClient, | ||
loadedProperties); | ||
dispatch(nodePropertiesLoaded(item, actor, properties)); | ||
@@ -99,3 +104,27 @@ } catch (e) { | ||
/* | ||
* 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(props: Props) { | ||
return { | ||
type: "ROOTS_CHANGED", | ||
data: props, | ||
}; | ||
} | ||
/* | ||
* This action will reset the `forceUpdate` flag in the state. | ||
*/ | ||
function forceUpdated() { | ||
return { | ||
type: "FORCE_UPDATED", | ||
}; | ||
} | ||
module.exports = { | ||
forceUpdated, | ||
nodeExpand, | ||
@@ -106,2 +135,3 @@ nodeCollapse, | ||
nodePropertiesLoaded, | ||
rootsChanged, | ||
}; |
@@ -46,2 +46,4 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
nodeIsWindow, | ||
nodeIsLongString, | ||
nodeHasFullText | ||
} = Utils.node; | ||
@@ -106,2 +108,3 @@ | ||
expandedPaths, | ||
focusedItem, | ||
loadedProperties, | ||
@@ -112,5 +115,18 @@ roots, | ||
if (roots !== nextProps.roots) { | ||
// Since the roots changed, we assume the properties did as well. Thus we can clear | ||
// the cachedNodes to avoid bugs and memory leaks. | ||
// Since the roots changed, we assume the properties did as well, so we need to | ||
// cleanup the component internal state. | ||
// We can clear the cachedNodes to avoid bugs and memory leaks. | ||
this.cachedNodes.clear(); | ||
// The rootsChanged action will be handled in a middleware to release the actors | ||
// of the old roots, as well as cleanup the state properties (expandedPaths, | ||
// loadedProperties, …). | ||
this.props.rootsChanged(nextProps); | ||
// We don't render right away since the state is going to be changed by the | ||
// rootsChanged action. The `state.forceUpdate` flag will be set to `true` so we | ||
// can execute a new render cycle with the cleaned state. | ||
return false; | ||
} | ||
if (nextProps.forceUpdate === true) { | ||
return true; | ||
@@ -123,2 +139,3 @@ } | ||
// - OR the expanded paths number did not changed, but old and new sets differ | ||
// - OR the focused node changed. | ||
return loadedProperties.size !== nextProps.loadedProperties.size | ||
@@ -132,5 +149,13 @@ || ( | ||
[...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) | ||
); | ||
) | ||
|| focusedItem !== nextProps.focusedItem; | ||
} | ||
componentDidUpdate(prevProps) { | ||
if (this.props.forceUpdate) { | ||
// If the component was updated, we can then reset the forceUpdate flag. | ||
this.props.forceUpdated(); | ||
} | ||
} | ||
componentWillUnmount() { | ||
@@ -181,2 +206,3 @@ const { releaseActor } = this.props; | ||
createObjectClient, | ||
createLongStringClient, | ||
loadedProperties, | ||
@@ -196,3 +222,4 @@ nodeExpand, | ||
const actor = isRoot || !value ? null : value.actor; | ||
nodeExpand(item, actor, loadedProperties, createObjectClient); | ||
nodeExpand(item, actor, loadedProperties, createObjectClient, | ||
createLongStringClient); | ||
} else { | ||
@@ -205,8 +232,13 @@ nodeCollapse(item); | ||
const { | ||
focusable = true, | ||
focusedItem, | ||
nodeFocus, | ||
onFocus, | ||
} = this.props; | ||
if (focusedItem !== item && onFocus) { | ||
onFocus(item); | ||
if (focusable && focusedItem !== item) { | ||
nodeFocus(item); | ||
if (focusedItem !== item && onFocus) { | ||
onFocus(item); | ||
} | ||
} | ||
@@ -219,3 +251,3 @@ } | ||
}) { | ||
let label = item.name; | ||
const label = item.name; | ||
const isPrimitive = nodeIsPrimitive(item); | ||
@@ -296,7 +328,8 @@ | ||
|| nodeIsMapEntry(item) | ||
|| nodeIsLongString(item) | ||
|| isPrimitive | ||
) { | ||
let repsProp = {...this.props}; | ||
let repProps = {...this.props}; | ||
if (depth > 0) { | ||
repsProp.mode = this.props.mode === MODE.LONG | ||
repProps.mode = this.props.mode === MODE.LONG | ||
? MODE.SHORT | ||
@@ -306,8 +339,14 @@ : MODE.TINY; | ||
if (expanded) { | ||
repsProp.mode = MODE.TINY; | ||
repProps.mode = MODE.TINY; | ||
} | ||
if (nodeIsLongString(item)) { | ||
repProps.member = { | ||
open: nodeHasFullText(item) && expanded | ||
}; | ||
} | ||
return { | ||
label, | ||
value: Utils.renderRep(item, repsProp) | ||
value: Utils.renderRep(item, repProps) | ||
}; | ||
@@ -365,2 +404,3 @@ } | ||
const { | ||
onCmdCtrlClick, | ||
onDoubleClick, | ||
@@ -385,10 +425,25 @@ dimTopLevelWindow, | ||
onClick: e => { | ||
e.stopPropagation(); | ||
// If the user selected text, bail out. | ||
if (Utils.selection.documentHasSelection()) { | ||
if (e.metaKey && onCmdCtrlClick) { | ||
onCmdCtrlClick(item, { | ||
depth, | ||
event: e, | ||
focused, | ||
expanded, | ||
}); | ||
e.stopPropagation(); | ||
return; | ||
} | ||
this.setExpanded(item, !expanded); | ||
// If this click happened because the user selected some text, bail out. | ||
// Note that if the user selected some text before and then clicks here, | ||
// the previously selected text will be first unselected, unless the user | ||
// clicked on the arrow itself. Indeed because the arrow is an image, clicking on | ||
// it does not remove any existing text selection. So we need to also check if | ||
// teh arrow was clicked. | ||
if ( | ||
Utils.selection.documentHasSelection() | ||
&& !(e.target && e.target.matches && e.target.matches(".arrow")) | ||
) { | ||
e.stopPropagation(); | ||
} | ||
}, | ||
@@ -437,3 +492,3 @@ }; | ||
autoExpandDepth = 1, | ||
disabledFocus, | ||
focusable = true, | ||
disableWrap = false, | ||
@@ -453,3 +508,2 @@ expandedPaths, | ||
autoExpandDepth, | ||
disabledFocus, | ||
@@ -467,3 +521,3 @@ isExpanded: item => expandedPaths && expandedPaths.has(item.path), | ||
onCollapse: item => this.setExpanded(item, false), | ||
onFocus: this.focusItem, | ||
onFocus: focusable ? this.focusItem : null, | ||
@@ -479,4 +533,8 @@ renderItem: this.renderTreeItem | ||
expandedPaths: state.expandedPaths, | ||
focusedItem: state.focusedItem, | ||
// If the root changes, we want to pass a possibly new focusedItem property | ||
focusedItem: state.roots !== props.roots | ||
? props.focusedItem | ||
: state.focusedItem, | ||
loadedProperties: state.loadedProperties, | ||
forceUpdate: state.forceUpdate, | ||
}; | ||
@@ -483,0 +541,0 @@ } |
@@ -43,2 +43,6 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
if (type === "NODE_FOCUS") { | ||
if (state.focusedItem === data.node) { | ||
return state; | ||
} | ||
return cloneState({ | ||
@@ -49,2 +53,8 @@ focusedItem: data.node | ||
if (type === "FORCE_UPDATED") { | ||
return cloneState({ | ||
forceUpdate: false, | ||
}); | ||
} | ||
return state; | ||
@@ -51,0 +61,0 @@ } |
@@ -6,3 +6,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
// @flow | ||
const { applyMiddleware, createStore } = require("redux"); | ||
const { applyMiddleware, createStore, compose } = require("redux"); | ||
const {thunk} = require("../shared/redux/middleware/thunk"); | ||
@@ -23,2 +23,3 @@ const {waitUntilService} = require("../shared/redux/middleware/waitUntilService"); | ||
loadedProperties: new Map(), | ||
forceUpdated: false, | ||
...overrides, | ||
@@ -28,4 +29,29 @@ }; | ||
function enableStateReinitializer(props) { | ||
return next => (innerReducer, initialState, enhancer) => { | ||
function reinitializerEnhancer(state, action) { | ||
if (action.type !== "ROOTS_CHANGED") { | ||
return innerReducer(state, action); | ||
} | ||
if (props.releaseActor && initialState.actors) { | ||
initialState.actors.forEach(props.releaseActor); | ||
} | ||
return { | ||
...action.data, | ||
actors: new Set(), | ||
expandedPaths: new Set(), | ||
loadedProperties: new Map(), | ||
// Indicates to the component that we do want to render on the next render cycle. | ||
forceUpdate: true, | ||
}; | ||
} | ||
return next(reinitializerEnhancer, initialState, enhancer); | ||
}; | ||
} | ||
module.exports = (props : Props) => { | ||
const middlewares = [thunk]; | ||
if (props.injectWaitService) { | ||
@@ -38,4 +64,7 @@ middlewares.push(waitUntilService); | ||
createInitialState(props), | ||
applyMiddleware(...middlewares) | ||
compose( | ||
applyMiddleware(...middlewares), | ||
enableStateReinitializer(props) | ||
) | ||
); | ||
}; |
@@ -277,11 +277,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
it("updates when the root changes", () => { | ||
it("updates when the root changes", async () => { | ||
let root = { | ||
path: "root", | ||
contents: { | ||
value: gripRepStubs.get("testMoreThanMaxProps") | ||
} | ||
}; | ||
let oi = mount(ObjectInspector(generateDefaults({ | ||
roots: [{ | ||
path: "root", | ||
contents: { | ||
value: gripRepStubs.get("testMoreThanMaxProps") | ||
} | ||
}], | ||
roots: [root], | ||
mode: MODE.LONG, | ||
focusedItem: root, | ||
injectWaitService: true, | ||
}))); | ||
@@ -291,10 +294,16 @@ | ||
root = { | ||
path: "root-2", | ||
contents: { | ||
value: gripRepStubs.get("testMaxProps") | ||
} | ||
}; | ||
let onComponentUpdated = waitForDispatch(oi.instance().getStore(), "FORCE_UPDATED"); | ||
oi.setProps({ | ||
roots: [{ | ||
path: "root-2", | ||
contents: { | ||
value: gripRepStubs.get("testMaxProps") | ||
} | ||
}] | ||
roots: [root], | ||
focusedItem: root, | ||
}); | ||
await onComponentUpdated; | ||
oi.update(); | ||
expect(formatObjectInspector(oi)).toMatchSnapshot(); | ||
@@ -321,6 +330,8 @@ }); | ||
mode: MODE.LONG, | ||
injectWaitService: true, | ||
}))); | ||
oi.find(".node").at(0).simulate("click"); | ||
const oldTree = formatObjectInspector(oi); | ||
const oldTree = formatObjectInspector(oi); | ||
let onComponentUpdated = waitForDispatch(oi.instance().getStore(), "FORCE_UPDATED"); | ||
oi.setProps({ | ||
@@ -339,4 +350,6 @@ roots: [{ | ||
await onComponentUpdated; | ||
oi.update(); | ||
expect(formatObjectInspector(oi)).not.toBe(oldTree); | ||
}); | ||
}); |
@@ -44,2 +44,23 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
it("does not call the onFocus when given focus but focusable is false", () => { | ||
const stub = gripRepStubs.get("testMaxProps"); | ||
const onFocus = jest.fn(); | ||
const oi = mount(ObjectInspector(generateDefaults({ | ||
focusable: true, | ||
roots: [{ | ||
path: "root", | ||
contents: { | ||
value: stub | ||
} | ||
}], | ||
onFocus, | ||
}))); | ||
const node = oi.find(".node").first(); | ||
node.simulate("focus"); | ||
expect(onFocus.mock.calls.length).toBe(0); | ||
}); | ||
it("calls the onDoubleClick prop function when provided one and double clicked", () => { | ||
@@ -65,2 +86,22 @@ const stub = gripRepStubs.get("testMaxProps"); | ||
it("calls the onCmdCtrlClick prop function when provided and cmd/ctrl-clicked", () => { | ||
const stub = gripRepStubs.get("testMaxProps"); | ||
const onCmdCtrlClick = jest.fn(); | ||
const oi = mount(ObjectInspector(generateDefaults({ | ||
roots: [{ | ||
path: "root", | ||
contents: { | ||
value: stub | ||
} | ||
}], | ||
onCmdCtrlClick, | ||
}))); | ||
const node = oi.find(".node").first(); | ||
node.simulate("click", { metaKey: true }); | ||
expect(onCmdCtrlClick.mock.calls.length).toBe(1); | ||
}); | ||
it("calls the onLabel prop function when provided one and label clicked", () => { | ||
@@ -67,0 +108,0 @@ const stub = gripRepStubs.get("testMaxProps"); |
@@ -208,2 +208,25 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
it("does expand if the user selected some text and clicked the arrow", async () => { | ||
const wrapper = mount(ObjectInspector(generateDefaults({ | ||
injectWaitService: true, | ||
loadedProperties: new Map([ | ||
["root-1", gripPropertiesStubs.get("proto-properties-symbols")] | ||
]) | ||
}))); | ||
const store = wrapper.instance().getStore(); | ||
expect(formatObjectInspector(wrapper)).toMatchSnapshot(); | ||
let nodes = wrapper.find(".node"); | ||
// Set a selection using the mock. | ||
getSelection().setMockSelection("test"); | ||
const root1 = nodes.at(0); | ||
root1.find("img.arrow").simulate("click"); | ||
expect(store.getState().expandedPaths.has("root-1")).toBeTruthy(); | ||
expect(formatObjectInspector(wrapper)).toMatchSnapshot(); | ||
// Clear the selection for other tests. | ||
getSelection().setMockSelection(); | ||
}); | ||
it("does not throw when expanding a block node", async () => { | ||
@@ -210,0 +233,0 @@ const blockNode = createNode({ |
@@ -15,2 +15,5 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
const stub = gripRepStubs.get("testMoreThanMaxProps"); | ||
const { | ||
waitForDispatch, | ||
} = require("../test-utils"); | ||
@@ -46,2 +49,30 @@ function generateDefaults(overrides) { | ||
}); | ||
it("calls release actors when the roots prop changed", async () => { | ||
const releaseActor = jest.fn(); | ||
const props = generateDefaults({ | ||
releaseActor, | ||
actors: new Set(["actor 1", "actor 2"]), | ||
injectWaitService: true, | ||
}); | ||
const oi = ObjectInspector(props); | ||
const wrapper = mount(oi); | ||
const store = wrapper.instance().getStore(); | ||
const onRootsChanged = waitForDispatch(store, "ROOTS_CHANGED"); | ||
wrapper.setProps({ | ||
roots: [{ | ||
path: "root-2", | ||
contents: { | ||
value: gripRepStubs.get("testMaxProps") | ||
} | ||
}] | ||
}); | ||
await onRootsChanged; | ||
expect(releaseActor.mock.calls.length).toBe(2); | ||
expect(releaseActor.mock.calls[0][0]).toBe("actor 1"); | ||
expect(releaseActor.mock.calls[1][0]).toBe("actor 2"); | ||
}); | ||
}); |
@@ -26,3 +26,4 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
function formatObjectInspector(wrapper: Object) { | ||
return wrapper.find(".tree-node") | ||
const hasFocusedNode = wrapper.find(".tree-node.focused").length > 0; | ||
const textTree = wrapper.find(".tree-node") | ||
.map(node => { | ||
@@ -42,5 +43,14 @@ const indentStr = "| ".repeat((node.prop("aria-level") || 1) - 1); | ||
: ""; | ||
return `${indentStr}${arrowStr}${icon}${getSanitizedNodeText(node)}`; | ||
let text = `${indentStr}${arrowStr}${icon}${getSanitizedNodeText(node)}`; | ||
if (!hasFocusedNode) { | ||
return text; | ||
} | ||
return node.hasClass("focused") | ||
? `[ ${text} ]` | ||
: ` ${text}`; | ||
}) | ||
.join("\n"); | ||
// Wrap the text representation in new lines so it keeps alignment between | ||
// tree nodes. | ||
return `\n${textTree}\n`; | ||
} | ||
@@ -47,0 +57,0 @@ |
@@ -9,3 +9,4 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
safeGetterValues?: Object, | ||
prototype?: Object | ||
prototype?: Object, | ||
fullText?: string, | ||
}; | ||
@@ -55,2 +56,17 @@ | ||
export type LongStringClient = { | ||
substring: ( | ||
start: number, | ||
end: number, | ||
response: { | ||
substring?: string, | ||
error?: Error, | ||
message?: string, | ||
}) => void, | ||
} | ||
export type CreateObjectClient = (RdpGrip) => ObjectClient; | ||
export type CreateLongStringClient = (RdpGrip) => LongStringClient; | ||
export type CachedNodes = Map<Path, Array<Node>>; | ||
@@ -69,3 +85,3 @@ | ||
autoExpandDepth: number, | ||
disabledFocus: boolean, | ||
focusable: boolean, | ||
itemHeight: number, | ||
@@ -78,3 +94,4 @@ inline: boolean, | ||
releaseActor: string => void, | ||
createObjectClient: RdpGrip => ObjectClient, | ||
createObjectClient: CreateObjectClient, | ||
createLongStringClient: CreateLongStringClient, | ||
onFocus: ?(Node) => any, | ||
@@ -89,2 +106,11 @@ onDoubleClick: ?( | ||
) => any, | ||
onCmdCtrlClick: ?( | ||
item: Node, | ||
options: { | ||
depth: number, | ||
event: SyntheticEvent, | ||
focused: boolean, | ||
expanded: boolean | ||
} | ||
) => any, | ||
onLabelClick: ?( | ||
@@ -91,0 +117,0 @@ item: Node, |
@@ -10,2 +10,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
PropertiesIterator, | ||
NodeContents, LongStringClient, | ||
} from "../types"; | ||
@@ -84,2 +85,24 @@ | ||
async function getFullText( | ||
longStringClient: LongStringClient, | ||
object: NodeContents, | ||
) : Promise<{fullText?: string}> { | ||
const { initial, length } = object; | ||
return new Promise((resolve, reject) => { | ||
longStringClient.substring(initial.length, length, response => { | ||
if (response.error) { | ||
console.error("LongStringClient.substring", | ||
response.error + ": " + response.message); | ||
reject({}); | ||
return; | ||
} | ||
resolve({ | ||
fullText: initial + response.substring | ||
}); | ||
}); | ||
}); | ||
} | ||
function iteratorSlice( | ||
@@ -94,2 +117,6 @@ iterator: PropertiesIterator, | ||
: iterator.count; | ||
if (count === 0) { | ||
return Promise.resolve({}); | ||
} | ||
return iterator.slice(start, count); | ||
@@ -104,2 +131,3 @@ } | ||
getPrototype, | ||
getFullText, | ||
}; |
@@ -11,2 +11,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
enumSymbols, | ||
getFullText, | ||
} = require("./client"); | ||
@@ -28,10 +29,11 @@ | ||
nodeNeedsNumericalBuckets, | ||
nodeIsLongString | ||
} = require("./node"); | ||
import type { | ||
CreateLongStringClient, | ||
CreateObjectClient, | ||
GripProperties, | ||
LoadedProperties, | ||
Node, | ||
ObjectClient, | ||
RdpGrip, | ||
} from "../types"; | ||
@@ -41,3 +43,4 @@ | ||
item : Node, | ||
createObjectClient : (RdpGrip) => ObjectClient, | ||
createObjectClient : CreateObjectClient, | ||
createLongStringClient : CreateLongStringClient, | ||
loadedProperties : LoadedProperties, | ||
@@ -76,2 +79,6 @@ ) : Promise<GripProperties> { | ||
if (shouldLoadItemFullText(item, loadedProperties)) { | ||
promises.push(getFullText(createLongStringClient(value), value)); | ||
} | ||
return Promise.all(promises).then(mergeResponses); | ||
@@ -95,2 +102,6 @@ } | ||
} | ||
if (response.fullText) { | ||
data.fullText = response.fullText; | ||
} | ||
} | ||
@@ -162,3 +173,4 @@ | ||
&& !nodeHasAccessors(item) | ||
&& !nodeIsPrimitive(item); | ||
&& !nodeIsPrimitive(item) | ||
&& !nodeIsLongString(item); | ||
} | ||
@@ -180,5 +192,13 @@ | ||
&& !nodeIsPrimitive(item) | ||
&& !nodeIsLongString(item) | ||
&& !nodeIsProxy(item); | ||
} | ||
function shouldLoadItemFullText( | ||
item: Node, | ||
loadedProperties: LoadedProperties = new Map() | ||
) { | ||
return !loadedProperties.has(item.path) && nodeIsLongString(item); | ||
} | ||
module.exports = { | ||
@@ -192,2 +212,3 @@ loadItemProperties, | ||
shouldLoadItemSymbols, | ||
shouldLoadItemFullText | ||
}; |
@@ -6,3 +6,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
// @flow | ||
const { get, has } = require("lodash"); | ||
const { maybeEscapePropertyName } = require("../../reps/rep-utils"); | ||
@@ -14,2 +14,3 @@ const ArrayRep = require("../../reps/array"); | ||
const ErrorRep = require("../../reps/error"); | ||
const { isLongString } = require("../../reps/string"); | ||
@@ -58,8 +59,8 @@ const MAX_NUMERICAL_PROPERTIES = 100; | ||
) : RdpGrip | NodeContents { | ||
if (has(item, "contents.value")) { | ||
return get(item, "contents.value"); | ||
if (item && item.contents && item.contents.hasOwnProperty("value")) { | ||
return item.contents.value; | ||
} | ||
if (has(item, "contents.getterValue")) { | ||
return get(item, "contents.getterValue", undefined); | ||
if (item && item.contents && item.contents.hasOwnProperty("getterValue")) { | ||
return item.contents.getterValue; | ||
} | ||
@@ -147,3 +148,4 @@ | ||
&& !nodeHasAccessors(item) | ||
&& !nodeIsBucket(item); | ||
&& !nodeIsBucket(item) | ||
&& !nodeIsLongString(item); | ||
} | ||
@@ -220,2 +222,15 @@ | ||
function nodeIsLongString( | ||
item: Node | ||
) : boolean { | ||
return isLongString(getValue(item)); | ||
} | ||
function nodeHasFullText( | ||
item: Node | ||
) : boolean { | ||
const value = getValue(item); | ||
return nodeIsLongString(item) && value.hasOwnProperty("fullText"); | ||
} | ||
function nodeHasAccessors(item: Node) : boolean { | ||
@@ -244,3 +259,4 @@ return !!getNodeGetter(item) || !!getNodeSetter(item); | ||
|| value.class === "WeakMap" | ||
|| value.class === "WeakSet"; | ||
|| value.class === "WeakSet" | ||
|| value.class === "Storage"; | ||
} | ||
@@ -412,7 +428,11 @@ | ||
function getNodeGetter(item: Node): ?Object { | ||
return get(item, "contents.get", undefined); | ||
return item && item.contents | ||
? item.contents.get | ||
: undefined; | ||
} | ||
function getNodeSetter(item: Node): ?Object { | ||
return get(item, "contents.set", undefined); | ||
return item && item.contents | ||
? item.contents.set | ||
: undefined; | ||
} | ||
@@ -609,2 +629,17 @@ | ||
function setNodeFullText( | ||
loadedProps: GripProperties, | ||
node: Node | ||
) : Node { | ||
if (nodeHasFullText(node)) { | ||
return node; | ||
} | ||
if (nodeIsLongString(node)) { | ||
node.contents.value.fullText = loadedProps.fullText; | ||
} | ||
return node; | ||
} | ||
function makeNodeForPrototype( | ||
@@ -734,5 +769,9 @@ objProps: GripProperties, | ||
if (nodeIsLongString(item) && hasLoadedProps) { | ||
// Set longString object's fullText to fetched one. | ||
return addToCache(setNodeFullText(loadedProps, item)); | ||
} | ||
if (nodeNeedsNumericalBuckets(item) && hasLoadedProps) { | ||
// Even if we have numerical buckets, we should have loaded non indexed properties, | ||
// like length for example. | ||
const bucketNodes = makeNumericalBuckets(item); | ||
@@ -837,2 +876,4 @@ return addToCache(bucketNodes.concat(makeNodesForProperties(loadedProps, item))); | ||
nodeIsError, | ||
nodeIsLongString, | ||
nodeHasFullText, | ||
nodeIsFunction, | ||
@@ -839,0 +880,0 @@ nodeIsGetter, |
@@ -5,4 +5,2 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
const {ELEMENT_NODE} = require("../../shared/dom-node-constants"); | ||
function documentHasSelection() : boolean { | ||
@@ -14,19 +12,2 @@ const selection = getSelection(); | ||
const { | ||
anchorNode, | ||
focusNode, | ||
} = selection; | ||
// When clicking the arrow, which is an inline svg element, the selection do have a type | ||
// of "Range". We need to have an explicit case when the anchor and the focus node are | ||
// the same and they have an arrow ancestor. | ||
if ( | ||
focusNode && | ||
focusNode === anchorNode && | ||
focusNode.nodeType == ELEMENT_NODE && | ||
focusNode.closest(".arrow") | ||
) { | ||
return false; | ||
} | ||
return selection.type === "Range"; | ||
@@ -33,0 +14,0 @@ } |
@@ -25,4 +25,6 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
object: PropTypes.object.isRequired, | ||
inspectIconTitle: PropTypes.string, | ||
// @TODO Change this to Object.values once it's supported in Node's version of V8 | ||
mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])), | ||
onDOMNodeClick: PropTypes.func, | ||
onDOMNodeMouseOver: PropTypes.func, | ||
@@ -36,3 +38,5 @@ onDOMNodeMouseOut: PropTypes.func, | ||
object, | ||
inspectIconTitle, | ||
mode, | ||
onDOMNodeClick, | ||
onDOMNodeMouseOver, | ||
@@ -52,2 +56,8 @@ onDOMNodeMouseOut, | ||
if (isInTree) { | ||
if (onDOMNodeClick) { | ||
Object.assign(baseConfig, { | ||
onClick: _ => onDOMNodeClick(object) | ||
}); | ||
} | ||
if (onDOMNodeMouseOver) { | ||
@@ -69,4 +79,10 @@ Object.assign(baseConfig, { | ||
// TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands | ||
title: "Click to select the node in the inspector", | ||
onClick: (e) => onInspectIconClick(object, e) | ||
title: inspectIconTitle || "Click to select the node in the inspector", | ||
onClick: e => { | ||
if (onDOMNodeClick) { | ||
e.stopPropagation(); | ||
} | ||
onInspectIconClick(object, e); | ||
} | ||
}); | ||
@@ -83,3 +99,8 @@ } | ||
function getElements(grip, mode) { | ||
let {attributes, nodeName} = grip.preview; | ||
let { | ||
attributes, | ||
nodeName, | ||
isAfterPseudoElement, | ||
isBeforePseudoElement, | ||
} = grip.preview; | ||
const nodeNameElement = span({ | ||
@@ -89,2 +110,8 @@ className: "tag-name" | ||
if (isAfterPseudoElement || isBeforePseudoElement) { | ||
return [ | ||
span({ className: "attrName" }, `::${ isAfterPseudoElement ? "after" : "before" }`) | ||
]; | ||
} | ||
if (mode === MODE.TINY) { | ||
@@ -109,2 +136,3 @@ let elements = [nodeNameElement]; | ||
} | ||
let attributeKeys = Object.keys(attributes); | ||
@@ -111,0 +139,0 @@ if (attributeKeys.includes("class")) { |
@@ -53,3 +53,5 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
} | ||
return (grip && grip.type === "mapEntry" && grip.preview); | ||
return grip && | ||
(grip.type === "mapEntry" || grip.type === "storageEntry") && | ||
grip.preview; | ||
} | ||
@@ -56,0 +58,0 @@ |
@@ -6,3 +6,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
// Dependencies | ||
const validProtocols = /^(http|https|ftp|data|resource|chrome):/i; | ||
const validProtocols = /(http|https|ftp|data|resource|chrome):/i; | ||
const tokenSplitRegex = /(\s|\'|\"|\\)+/; | ||
@@ -363,8 +363,8 @@ const ELLIPSIS = "\u2026"; | ||
function containsURL(grip) { | ||
if (typeof grip !== "string") { | ||
// An URL can't be shorter than 5 char (e.g. "ftp:"). | ||
if (typeof grip !== "string" || grip.length < 5) { | ||
return false; | ||
} | ||
let tokens = grip.split(tokenSplitRegex); | ||
return tokens.some(isURL); | ||
return validProtocols.test(grip); | ||
} | ||
@@ -371,0 +371,0 @@ |
@@ -77,3 +77,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
/** | ||
* Generic rep that is using for rendering native JS types or an object. | ||
* Generic rep that is used for rendering native JS types or an object. | ||
* The right template used for rendering is picked automatically according | ||
@@ -102,3 +102,3 @@ * to the current value type. The value must be passed is as 'object' | ||
* | ||
* @param defaultObject {React.Component} The default template | ||
* @param defaultRep {React.Component} The default template | ||
* that should be used to render given object if none is found. | ||
@@ -105,0 +105,0 @@ * |
@@ -32,3 +32,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
cropLimit: PropTypes.number.isRequired, | ||
member: PropTypes.string, | ||
member: PropTypes.object, | ||
object: PropTypes.object.isRequired, | ||
@@ -48,3 +48,3 @@ openLink: PropTypes.func, | ||
member, | ||
openLink, | ||
openLink | ||
} = props; | ||
@@ -55,3 +55,4 @@ | ||
const isLong = isLongString(object); | ||
const shouldCrop = (!member || !member.open) && cropLimit && text.length > cropLimit; | ||
const isOpen = member && member.open; | ||
const shouldCrop = !isOpen && cropLimit && text.length > cropLimit; | ||
@@ -63,2 +64,7 @@ if (isLong) { | ||
}, text); | ||
const { fullText } = object; | ||
if (isOpen && fullText) { | ||
text = fullText; | ||
} | ||
} | ||
@@ -97,7 +103,6 @@ | ||
shouldCrop, | ||
cropLimit | ||
cropLimit, | ||
} = opts; | ||
const { | ||
fullText, | ||
initial, | ||
@@ -109,3 +114,3 @@ length, | ||
? initial.substring(0, cropLimit) | ||
: fullText || initial; | ||
: initial; | ||
@@ -112,0 +117,0 @@ if (text.length < length) { |
@@ -235,2 +235,28 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
stubs.set("BeforePseudoElement", { | ||
"type": "object", | ||
"actor": "server1.conn1.child1/obj27", | ||
"preview": { | ||
"kind": "DOMNode", | ||
"nodeType": 1, | ||
"nodeName": "_moz_generated_content_before", | ||
"attributes": {}, | ||
"attributesLength": 0, | ||
"isBeforePseudoElement": true, | ||
} | ||
}); | ||
stubs.set("AfterPseudoElement", { | ||
"type": "object", | ||
"actor": "server1.conn1.child1/obj28", | ||
"preview": { | ||
"kind": "DOMNode", | ||
"nodeType": 1, | ||
"nodeName": "_moz_generated_content_after", | ||
"attributes": {}, | ||
"attributesLength": 0, | ||
"isAfterPseudoElement": true, | ||
} | ||
}); | ||
module.exports = stubs; |
@@ -123,2 +123,14 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
it("calls the expected function when click is fired on Rep", () => { | ||
const onDOMNodeClick = jest.fn(); | ||
const renderedComponent = shallow(ElementNode.rep({ | ||
object: stub, | ||
onDOMNodeClick | ||
})); | ||
renderedComponent.simulate("click"); | ||
expect(onDOMNodeClick.mock.calls.length).toEqual(1); | ||
}); | ||
it("calls the expected function when mouseout is fired on Rep", () => { | ||
@@ -349,1 +361,68 @@ const onDOMNodeMouseOut = jest.fn(); | ||
}); | ||
describe("ElementNode - : Before pseudo element", () => { | ||
const stub = stubs.get("BeforePseudoElement"); | ||
it("selects ElementNode Rep", () => { | ||
expect(getRep(stub)).toBe(ElementNode.rep); | ||
}); | ||
it("renders with expected text content", () => { | ||
const renderedComponent = shallow(ElementNode.rep({ | ||
object: stub | ||
})); | ||
expect(renderedComponent.text()).toEqual("::before"); | ||
}); | ||
it("renders with expected text content in tiny mode", () => { | ||
const renderedComponent = shallow(ElementNode.rep({ | ||
object: stub, | ||
mode: MODE.TINY | ||
})); | ||
expect(renderedComponent.text()).toEqual("::before"); | ||
}); | ||
}); | ||
describe("ElementNode - After pseudo element", () => { | ||
const stub = stubs.get("AfterPseudoElement"); | ||
it("selects ElementNode Rep", () => { | ||
expect(getRep(stub)).toBe(ElementNode.rep); | ||
}); | ||
it("renders with expected text content", () => { | ||
const renderedComponent = shallow(ElementNode.rep({ | ||
object: stub | ||
})); | ||
expect(renderedComponent.text()).toEqual("::after"); | ||
}); | ||
it("renders with expected text content in tiny mode", () => { | ||
const renderedComponent = shallow(ElementNode.rep({ | ||
object: stub, | ||
mode: MODE.TINY | ||
})); | ||
expect(renderedComponent.text()).toEqual("::after"); | ||
}); | ||
}); | ||
describe("ElementNode - Inspect icon title", () => { | ||
const stub = stubs.get("Node"); | ||
it("renders with expected title", () => { | ||
const inspectIconTitle = "inspect icon title"; | ||
const renderedComponent = shallow(ElementNode.rep({ | ||
inspectIconTitle, | ||
object: stub, | ||
onInspectIconClick: jest.fn(), | ||
})); | ||
const iconNode = renderedComponent.find(".open-inspector"); | ||
expect(iconNode.prop("title")).toEqual(inspectIconTitle); | ||
}); | ||
}); |
@@ -12,2 +12,6 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
const { | ||
ELLIPSIS, | ||
} = require("../rep-utils"); | ||
const { | ||
expectActorAttribute, | ||
@@ -36,3 +40,4 @@ } = require("./test-helpers"); | ||
expect(renderedComponent.text()).toEqual(quoteNewlines(`"${stub.initial}…"`)); | ||
expect(renderedComponent.text()) | ||
.toEqual(quoteNewlines(`"${stub.initial}${ELLIPSIS}"`)); | ||
expectActorAttribute(renderedComponent, stub.actor); | ||
@@ -49,3 +54,3 @@ }); | ||
expect(renderedComponent.text()).toEqual(`"a\\naaaaaaaaaaaaaaaaaa…"`); | ||
expect(renderedComponent.text()).toEqual(`"a\\naaaaaaaaaaaaaaaaaa${ELLIPSIS}"`); | ||
}); | ||
@@ -62,3 +67,3 @@ | ||
expect(renderedComponent.text()) | ||
.toEqual(quoteNewlines(`"${stub.initial}…"`)); | ||
.toEqual(quoteNewlines(`"${stub.initial}${ELLIPSIS}"`)); | ||
}); | ||
@@ -86,3 +91,3 @@ | ||
expect(renderedComponent.text()).toEqual(`"a\\naaaaaaaaaaaaaaaaaa…"`); | ||
expect(renderedComponent.text()).toEqual(`"a\\naaaaaaaaaaaaaaaaaa${ELLIPSIS}"`); | ||
}); | ||
@@ -100,4 +105,5 @@ | ||
.toEqual('<span data-link-actor-id="server1.conn1.child1/longString58" ' + | ||
'class="objectBox objectBox-string">a\naaaaaaaaaaaaaaaaaa…</span>'); | ||
`class="objectBox objectBox-string">a\naaaaaaaaaaaaaaaaaa${ELLIPSIS}` + | ||
"</span>"); | ||
}); | ||
}); |
@@ -6,2 +6,3 @@ /* This Source Code Form is subject to the terms of the Mozilla Public | ||
const { shallow } = require("enzyme"); | ||
const { ELLIPSIS } = require("../rep-utils"); | ||
const { REPS } = require("../rep"); | ||
@@ -22,3 +23,3 @@ const { Rep } = REPS; | ||
}, | ||
result: "\"aaaaaaaaa…cccccc\\n\"" | ||
result: `\"aaaaaaaaa${ELLIPSIS}cccccc\\n\"` | ||
}, { | ||
@@ -75,2 +76,27 @@ name: "testMultilineOpen", | ||
result: "\"line 1\r\nline 2\n\tline 3\"" | ||
}, { | ||
name: "testIgnoreFullTextWhenOpen", | ||
props: { | ||
object: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | ||
fullText: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | ||
member: {open: true}, | ||
}, | ||
result: "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"" | ||
}, { | ||
name: "testIgnoreFullTextWithLimit", | ||
props: { | ||
object: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | ||
fullText: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | ||
cropLimit: 20, | ||
}, | ||
result: `\"aaaaaaaaa${ELLIPSIS}aaaaaaaa\"` | ||
}, { | ||
name: "testIgnoreFullTextWhenOpenWithLimit", | ||
props: { | ||
object: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | ||
fullText: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | ||
member: {open: true}, | ||
cropLimit: 20, | ||
}, | ||
result: "\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"" | ||
}]; | ||
@@ -77,0 +103,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
1224923
218
20893
16
+ Added@babel/runtime@7.24.6(transitive)
+ Addeddevtools-components@0.6.0(transitive)
- Removed@babel/runtime@7.24.7(transitive)
- Removeddevtools-components@0.4.1(transitive)
Updateddevtools-components@^0.6.0