зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1574192 - Initial watchpoints front end commit. r=nchevobbe
Differential Revision: https://phabricator.services.mozilla.com/D43487 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
2b19541fd2
Коммит
57718b6d7a
|
@ -7,7 +7,8 @@
|
|||
import type { GripProperties, Node, Props, ReduxAction } from "./types";
|
||||
|
||||
const { loadItemProperties } = require("./utils/load-properties");
|
||||
const { getLoadedProperties, getActors } = require("./reducer");
|
||||
const { getPathExpression, getValue } = require("./utils/node");
|
||||
const { getLoadedProperties, getActors, getWatchpoints } = require("./reducer");
|
||||
|
||||
type Dispatch = ReduxAction => void;
|
||||
|
||||
|
@ -73,6 +74,50 @@ function nodePropertiesLoaded(
|
|||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* This action adds a property watchpoint to an object
|
||||
*/
|
||||
function addWatchpoint(item, watchpoint: string) {
|
||||
return async function({ dispatch, client }: ThunkArgs) {
|
||||
const { parent, name } = item;
|
||||
const object = getValue(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 }: ThunkArgs) {
|
||||
const object = getValue(item.parent);
|
||||
const property = item.name;
|
||||
const path = item.parent.path;
|
||||
const actor = object.actor;
|
||||
|
||||
await client.removeWatchpoint(object, property);
|
||||
|
||||
dispatch({
|
||||
type: "REMOVE_WATCHPOINT",
|
||||
data: { path, property, actor },
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function closeObjectInspector() {
|
||||
return async ({ getState, client }: ThunkArg) => {
|
||||
releaseActors(getState(), client);
|
||||
|
@ -99,8 +144,14 @@ function rootsChanged(props: Props) {
|
|||
|
||||
function releaseActors(state, client) {
|
||||
const actors = getActors(state);
|
||||
const watchpoints = getWatchpoints(state);
|
||||
|
||||
for (const actor of actors) {
|
||||
client.releaseActor(actor);
|
||||
// Watchpoints are stored in object actors.
|
||||
// If we release the actor we lose the watchpoint.
|
||||
if (!watchpoints.has(actor)) {
|
||||
client.releaseActor(actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,4 +189,6 @@ module.exports = {
|
|||
nodeLoadProperties,
|
||||
nodePropertiesLoaded,
|
||||
rootsChanged,
|
||||
addWatchpoint,
|
||||
removeWatchpoint,
|
||||
};
|
||||
|
|
|
@ -55,3 +55,31 @@
|
|||
.tree-node.focused button.invoke-getter {
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
button.remove-set-watchpoint {
|
||||
mask: url("resource://devtools/client/debugger/images/webconsole-logpoint.svg")
|
||||
no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin: 0 4px 0px 20px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: var(--breakpoint-fill);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.remove-get-watchpoint {
|
||||
mask: url("resource://devtools/client/debugger/images/webconsole-logpoint.svg")
|
||||
no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin: 0 4px 0px 20px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: var(--purple-60);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -110,6 +110,12 @@ class ObjectInspector extends Component<Props> {
|
|||
return;
|
||||
}
|
||||
|
||||
for (const [path, properties] of nextProps.loadedProperties) {
|
||||
if (properties !== this.props.loadedProperties.get(path)) {
|
||||
this.cachedNodes.delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are new evaluations, we want to remove the existing cached
|
||||
// nodes from the cache.
|
||||
if (nextProps.evaluations > this.props.evaluations) {
|
||||
|
@ -134,6 +140,7 @@ class ObjectInspector extends Component<Props> {
|
|||
// - OR the focused node changed.
|
||||
// - OR the active node changed.
|
||||
return (
|
||||
loadedProperties !== nextProps.loadedProperties ||
|
||||
loadedProperties.size !== nextProps.loadedProperties.size ||
|
||||
evaluations.size !== nextProps.evaluations.size ||
|
||||
(expandedPaths.size !== nextProps.expandedPaths.size &&
|
||||
|
@ -271,7 +278,6 @@ class ObjectInspector extends Component<Props> {
|
|||
autoExpandAll,
|
||||
autoExpandDepth,
|
||||
initiallyExpanded,
|
||||
|
||||
isExpanded: item => expandedPaths && expandedPaths.has(item.path),
|
||||
isExpandable: this.isNodeExpandable,
|
||||
focused: this.focusedItem,
|
||||
|
|
|
@ -80,6 +80,12 @@ type Props = {
|
|||
};
|
||||
|
||||
class ObjectInspectorItem extends Component<Props> {
|
||||
static get defaultProps() {
|
||||
return {
|
||||
onContextMenu: () => {},
|
||||
};
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
getLabelAndValue(): {
|
||||
value?: string | Element,
|
||||
|
@ -203,6 +209,7 @@ class ObjectInspectorItem extends Component<Props> {
|
|||
onCmdCtrlClick,
|
||||
onDoubleClick,
|
||||
dimTopLevelWindow,
|
||||
onContextMenu,
|
||||
} = this.props;
|
||||
|
||||
const parentElementProps: Object = {
|
||||
|
@ -245,6 +252,7 @@ class ObjectInspectorItem extends Component<Props> {
|
|||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
onContextMenu: e => onContextMenu(e, item),
|
||||
};
|
||||
|
||||
if (onDoubleClick) {
|
||||
|
@ -292,6 +300,21 @@ class ObjectInspectorItem extends Component<Props> {
|
|||
);
|
||||
}
|
||||
|
||||
renderWatchpointButton() {
|
||||
const { item, removeWatchpoint } = this.props;
|
||||
|
||||
if (!item || !item.contents || !item.contents.watchpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const watchpoint = item.contents.watchpoint;
|
||||
return dom.button({
|
||||
className: `remove-${watchpoint}-watchpoint`,
|
||||
title: L10N.getStr("watchpoints.removeWatchpoint"),
|
||||
onClick: () => removeWatchpoint(item),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { arrow } = this.props;
|
||||
|
||||
|
@ -307,7 +330,8 @@ class ObjectInspectorItem extends Component<Props> {
|
|||
arrow,
|
||||
labelElement,
|
||||
delimiter,
|
||||
value
|
||||
value,
|
||||
this.renderWatchpointButton()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
const ObjectInspector = require("./components/ObjectInspector");
|
||||
const utils = require("./utils");
|
||||
const reducer = require("./reducer");
|
||||
const actions = require("./actions");
|
||||
|
||||
module.exports = { ObjectInspector, utils, reducer };
|
||||
module.exports = { ObjectInspector, utils, actions, reducer };
|
||||
|
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
import type { ReduxAction, State } from "./types";
|
||||
|
||||
function initialState() {
|
||||
function initialState(overrides) {
|
||||
return {
|
||||
expandedPaths: new Set(),
|
||||
loadedProperties: new Map(),
|
||||
evaluations: new Map(),
|
||||
actors: new Set(),
|
||||
watchpoints: new Map(),
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -33,6 +35,34 @@ function reducer(
|
|||
return cloneState({ expandedPaths });
|
||||
}
|
||||
|
||||
if (type == "SET_WATCHPOINT") {
|
||||
const { watchpoint, property, path } = data;
|
||||
const obj = state.loadedProperties.get(path);
|
||||
|
||||
return cloneState({
|
||||
loadedProperties: new Map(state.loadedProperties).set(
|
||||
path,
|
||||
updateObject(obj, property, watchpoint)
|
||||
),
|
||||
watchpoints: new Map(state.watchpoints).set(data.actor, data.watchpoint),
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "REMOVE_WATCHPOINT") {
|
||||
const { path, property, actor } = data;
|
||||
const obj = state.loadedProperties.get(path);
|
||||
const watchpoints = new Map(state.watchpoints);
|
||||
watchpoints.delete(actor);
|
||||
|
||||
return cloneState({
|
||||
loadedProperties: new Map(state.loadedProperties).set(
|
||||
path,
|
||||
updateObject(obj, property, null)
|
||||
),
|
||||
watchpoints: watchpoints,
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "NODE_PROPERTIES_LOADED") {
|
||||
return cloneState({
|
||||
actors: data.actor
|
||||
|
@ -66,12 +96,25 @@ function reducer(
|
|||
// NOTE: we clear the state on resume because otherwise the scopes pane
|
||||
// would be out of date. Bug 1514760
|
||||
if (type === "RESUME" || type == "NAVIGATE") {
|
||||
return initialState();
|
||||
return initialState({ watchpoints: state.watchpoints });
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function updateObject(obj, property, watchpoint) {
|
||||
return {
|
||||
...obj,
|
||||
ownProperties: {
|
||||
...obj.ownProperties,
|
||||
[property]: {
|
||||
...obj.ownProperties[property],
|
||||
watchpoint,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getObjectInspectorState(state) {
|
||||
return state.objectInspector;
|
||||
}
|
||||
|
@ -88,6 +131,10 @@ function getActors(state) {
|
|||
return getObjectInspectorState(state).actors;
|
||||
}
|
||||
|
||||
function getWatchpoints(state) {
|
||||
return getObjectInspectorState(state).watchpoints;
|
||||
}
|
||||
|
||||
function getLoadedProperties(state) {
|
||||
return getObjectInspectorState(state).loadedProperties;
|
||||
}
|
||||
|
@ -102,6 +149,7 @@ function getEvaluations(state) {
|
|||
|
||||
const selectors = {
|
||||
getActors,
|
||||
getWatchpoints,
|
||||
getEvaluations,
|
||||
getExpandedPathKeys,
|
||||
getExpandedPaths,
|
||||
|
|
|
@ -24,6 +24,7 @@ exports[`ObjectInspector - classnames has the expected class 1`] = `
|
|||
<div
|
||||
className="node object-node"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<span
|
||||
className="object-label"
|
||||
|
@ -69,6 +70,7 @@ exports[`ObjectInspector - classnames has the inline class when inline prop is t
|
|||
<div
|
||||
className="node object-node"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<span
|
||||
className="object-label"
|
||||
|
@ -114,6 +116,7 @@ exports[`ObjectInspector - classnames has the nowrap class when disableWrap prop
|
|||
<div
|
||||
className="node object-node"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<span
|
||||
className="object-label"
|
||||
|
|
|
@ -25,6 +25,7 @@ exports[`ObjectInspector - dimTopLevelWindow renders collapsed top-level window
|
|||
<div
|
||||
className="node object-node"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<button
|
||||
className="arrow"
|
||||
|
@ -80,6 +81,7 @@ exports[`ObjectInspector - dimTopLevelWindow renders sub-level window 1`] = `
|
|||
<div
|
||||
className="node object-node focused"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<button
|
||||
className="arrow expanded"
|
||||
|
@ -109,6 +111,7 @@ exports[`ObjectInspector - dimTopLevelWindow renders sub-level window 1`] = `
|
|||
<div
|
||||
className="node object-node"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<button
|
||||
className="arrow"
|
||||
|
@ -163,6 +166,7 @@ exports[`ObjectInspector - dimTopLevelWindow renders window as expected when dim
|
|||
<div
|
||||
className="node object-node lessen"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<button
|
||||
className="arrow"
|
||||
|
@ -218,6 +222,7 @@ exports[`ObjectInspector - dimTopLevelWindow renders window as expected when dim
|
|||
<div
|
||||
className="node object-node focused"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<button
|
||||
className="arrow expanded"
|
||||
|
@ -261,6 +266,7 @@ exports[`ObjectInspector - dimTopLevelWindow renders window as expected when dim
|
|||
<div
|
||||
className="node object-node lessen"
|
||||
onClick={[Function]}
|
||||
onContextMenu={[Function]}
|
||||
>
|
||||
<span
|
||||
className="object-label"
|
||||
|
|
|
@ -61,6 +61,7 @@ describe("release actors", () => {
|
|||
{
|
||||
initialState: {
|
||||
actors: new Set(["actor 1", "actor 2"]),
|
||||
watchpoints: new Map(),
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -20,11 +20,7 @@ describe("makeNumericalBuckets", () => {
|
|||
|
||||
expect(names).toEqual(["[0…99]", "[100…199]", "[200…233]"]);
|
||||
|
||||
expect(paths).toEqual([
|
||||
"root◦[0…99]",
|
||||
"root◦[100…199]",
|
||||
"root◦[200…233]",
|
||||
]);
|
||||
expect(paths).toEqual(["root◦[0…99]", "root◦[100…199]", "root◦[200…233]"]);
|
||||
});
|
||||
|
||||
// TODO: Re-enable when we have support for lonely node.
|
||||
|
@ -60,10 +56,7 @@ describe("makeNumericalBuckets", () => {
|
|||
|
||||
expect(names).toEqual(["[0…99]", "[100…101]"]);
|
||||
|
||||
expect(paths).toEqual([
|
||||
"root◦bucket_0-99",
|
||||
"root◦bucket_100-101",
|
||||
]);
|
||||
expect(paths).toEqual(["root◦bucket_0-99", "root◦bucket_100-101"]);
|
||||
});
|
||||
|
||||
it("creates sub-buckets when needed", () => {
|
||||
|
@ -134,9 +127,7 @@ describe("makeNumericalBuckets", () => {
|
|||
"[23300…23399]",
|
||||
"[23400…23455]",
|
||||
]);
|
||||
expect(lastBucketPaths[0]).toEqual(
|
||||
"root◦[23000…23455]◦[23000…23099]"
|
||||
);
|
||||
expect(lastBucketPaths[0]).toEqual("root◦[23000…23455]◦[23000…23099]");
|
||||
expect(lastBucketPaths[lastBucketPaths.length - 1]).toEqual(
|
||||
"root◦[23000…23455]◦[23400…23455]"
|
||||
);
|
||||
|
|
|
@ -816,6 +816,15 @@ function getChildren(options: {
|
|||
return addToCache(makeNodesForProperties(loadedProps, item));
|
||||
}
|
||||
|
||||
// Builds an expression that resolves to the value of the item in question
|
||||
// e.g. `b` in { a: { b: 2 } } resolves to `a.b`
|
||||
function getPathExpression(item) {
|
||||
if (item && item.parent) {
|
||||
return `${getPathExpression(item.parent)}.${item.name}`;
|
||||
}
|
||||
return item.name;
|
||||
}
|
||||
|
||||
function getParent(item: Node): Node | null {
|
||||
return item.parent;
|
||||
}
|
||||
|
@ -922,6 +931,7 @@ module.exports = {
|
|||
getChildrenWithEvaluations,
|
||||
getClosestGripNode,
|
||||
getClosestNonBucketNode,
|
||||
getPathExpression,
|
||||
getParent,
|
||||
getParentGripValue,
|
||||
getNonPrototypeParentGripValue,
|
||||
|
|
|
@ -22,6 +22,9 @@ import * as threads from "./threads";
|
|||
import * as toolbox from "./toolbox";
|
||||
import * as preview from "./preview";
|
||||
|
||||
// eslint-disable-next-line import/named
|
||||
import { objectInspector } from "devtools-reps";
|
||||
|
||||
export default {
|
||||
...ast,
|
||||
...navigation,
|
||||
|
@ -34,6 +37,7 @@ export default {
|
|||
...pause,
|
||||
...ui,
|
||||
...fileSearch,
|
||||
...objectInspector.actions,
|
||||
...projectTextSearch,
|
||||
...quickOpen,
|
||||
...sourceTree,
|
||||
|
|
|
@ -188,6 +188,25 @@ function removeXHRBreakpoint(path: string, method: string) {
|
|||
return currentThreadFront.removeXHRBreakpoint(path, method);
|
||||
}
|
||||
|
||||
function addWatchpoint(
|
||||
object: Grip,
|
||||
property: string,
|
||||
label: string,
|
||||
watchpointType: string
|
||||
) {
|
||||
if (currentTarget.traits.watchpoints) {
|
||||
const objectClient = createObjectClient(object);
|
||||
return objectClient.addWatchpoint(property, label, watchpointType);
|
||||
}
|
||||
}
|
||||
|
||||
function removeWatchpoint(object: Grip, property: string) {
|
||||
if (currentTarget.traits.watchpoints) {
|
||||
const objectClient = createObjectClient(object);
|
||||
return objectClient.removeWatchpoint(property);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the string key to use for a breakpoint location.
|
||||
// See also duplicate code in breakpoint-actor-map.js :(
|
||||
function locationKey(location: BreakpointLocation) {
|
||||
|
@ -514,6 +533,8 @@ const clientCommands = {
|
|||
setBreakpoint,
|
||||
setXHRBreakpoint,
|
||||
removeXHRBreakpoint,
|
||||
addWatchpoint,
|
||||
removeWatchpoint,
|
||||
removeBreakpoint,
|
||||
evaluate,
|
||||
evaluateInFrame,
|
||||
|
|
|
@ -260,7 +260,7 @@ export type DebuggerClient = {
|
|||
connect: () => Promise<*>,
|
||||
request: (packet: Object) => Promise<*>,
|
||||
attachConsole: (actor: String, listeners: Array<*>) => Promise<*>,
|
||||
createObjectClient: (grip: Grip) => {},
|
||||
createObjectClient: (grip: Grip) => ObjectClient,
|
||||
release: (actor: String) => {},
|
||||
};
|
||||
|
||||
|
@ -338,6 +338,12 @@ export type SourceClient = {
|
|||
*/
|
||||
export type ObjectClient = {
|
||||
getPrototypeAndProperties: () => any,
|
||||
addWatchpoint: (
|
||||
property: string,
|
||||
label: string,
|
||||
watchpointType: string
|
||||
) => {},
|
||||
removeWatchpoint: (property: string) => {},
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
// @flow
|
||||
import React, { PureComponent } from "react";
|
||||
import { showMenu } from "devtools-contextmenu";
|
||||
import { connect } from "../../utils/connect";
|
||||
import actions from "../../actions";
|
||||
import { createObjectClient } from "../../client/firefox";
|
||||
import { features } from "../../utils/prefs";
|
||||
|
||||
import {
|
||||
getSelectedSource,
|
||||
|
@ -45,6 +47,8 @@ type Props = {
|
|||
unHighlightDomElement: typeof actions.unHighlightDomElement,
|
||||
toggleMapScopes: typeof actions.toggleMapScopes,
|
||||
setExpandedScope: typeof actions.setExpandedScope,
|
||||
addWatchpoint: typeof actions.addWatchpoint,
|
||||
removeWatchpoint: typeof actions.removeWatchpoint,
|
||||
expandedScopes: string[],
|
||||
};
|
||||
|
||||
|
@ -111,6 +115,50 @@ class Scopes extends PureComponent<Props, State> {
|
|||
this.props.toggleMapScopes();
|
||||
};
|
||||
|
||||
onContextMenu = (event, item) => {
|
||||
const { addWatchpoint, removeWatchpoint } = this.props;
|
||||
|
||||
if (!features.watchpoints || !item.parent || !item.parent.contents) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!item.contents || item.contents.watchpoint) {
|
||||
const removeWatchpointItem = {
|
||||
id: "node-menu-remove-watchpoint",
|
||||
// NOTE: we're going to update the UI to add a "break on..."
|
||||
// sub menu. At that point we'll translate the strings. bug 1580591
|
||||
label: "Remove watchpoint",
|
||||
disabled: false,
|
||||
click: () => removeWatchpoint(item),
|
||||
};
|
||||
|
||||
const menuItems = [removeWatchpointItem];
|
||||
return showMenu(event, menuItems);
|
||||
}
|
||||
|
||||
// NOTE: we're going to update the UI to add a "break on..."
|
||||
// sub menu. At that point we'll translate the strings. bug 1580591
|
||||
const addSetWatchpointLabel = "Pause on set";
|
||||
const addGetWatchpointLabel = "Pause on get";
|
||||
|
||||
const addSetWatchpoint = {
|
||||
id: "node-menu-add-set-watchpoint",
|
||||
label: addSetWatchpointLabel,
|
||||
disabled: false,
|
||||
click: () => addWatchpoint(item, "set"),
|
||||
};
|
||||
|
||||
const addGetWatchpoint = {
|
||||
id: "node-menu-add-get-watchpoint",
|
||||
label: addGetWatchpointLabel,
|
||||
disabled: false,
|
||||
click: () => addWatchpoint(item, "get"),
|
||||
};
|
||||
|
||||
const menuItems = [addGetWatchpoint, addSetWatchpoint];
|
||||
showMenu(event, menuItems);
|
||||
};
|
||||
|
||||
renderScopesList() {
|
||||
const {
|
||||
cx,
|
||||
|
@ -147,6 +195,7 @@ class Scopes extends PureComponent<Props, State> {
|
|||
onInspectIconClick={grip => openElementInInspector(grip)}
|
||||
onDOMNodeMouseOver={grip => highlightDomElement(grip)}
|
||||
onDOMNodeMouseOut={grip => unHighlightDomElement(grip)}
|
||||
onContextMenu={this.onContextMenu}
|
||||
setExpanded={(path, expand) => setExpandedScope(cx, path, expand)}
|
||||
initiallyExpanded={initiallyExpanded}
|
||||
/>
|
||||
|
@ -223,5 +272,7 @@ export default connect(
|
|||
unHighlightDomElement: actions.unHighlightDomElement,
|
||||
toggleMapScopes: actions.toggleMapScopes,
|
||||
setExpandedScope: actions.setExpandedScope,
|
||||
addWatchpoint: actions.addWatchpoint,
|
||||
removeWatchpoint: actions.removeWatchpoint,
|
||||
}
|
||||
)(Scopes);
|
||||
|
|
|
@ -644,6 +644,16 @@ export function getInlinePreviews(
|
|||
];
|
||||
}
|
||||
|
||||
export function getSelectedInlinePreviews(state: State) {
|
||||
const thread = getCurrentThread(state);
|
||||
const frameId = getSelectedFrameId(state, thread);
|
||||
if (!frameId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return getInlinePreviews(state, thread, frameId);
|
||||
}
|
||||
|
||||
export function getInlinePreviewExpression(
|
||||
state: State,
|
||||
thread: ThreadId,
|
||||
|
|
|
@ -17,6 +17,8 @@ const reasons = {
|
|||
resumeLimit: "whyPaused.resumeLimit",
|
||||
breakpointConditionThrown: "whyPaused.breakpointConditionThrown",
|
||||
eventBreakpoint: "whyPaused.eventBreakpoint",
|
||||
getWatchpoint: "whyPaused.getWatchpoint",
|
||||
setWatchpoint: "whyPaused.setWatchpoint",
|
||||
mutationBreakpoint: "whyPaused.mutationBreakpoint",
|
||||
interrupted: "whyPaused.interrupted",
|
||||
replayForcedPause: "whyPaused.replayForcedPause",
|
||||
|
|
|
@ -72,6 +72,7 @@ if (isDevelopment()) {
|
|||
pref("devtools.debugger.log-actions", true);
|
||||
pref("devtools.debugger.features.overlay-step-buttons", true);
|
||||
pref("devtools.debugger.features.log-event-breakpoints", false);
|
||||
pref("devtools.debugger.features.watchpoints", false);
|
||||
}
|
||||
|
||||
export const prefs = new PrefsHelper("devtools", {
|
||||
|
@ -140,6 +141,7 @@ export const features = new PrefsHelper("devtools.debugger.features", {
|
|||
showOverlayStepButtons: ["Bool", "overlay-step-buttons"],
|
||||
inlinePreview: ["Bool", "inline-preview"],
|
||||
logEventBreakpoints: ["Bool", "log-event-breakpoints"],
|
||||
watchpoints: ["Bool", "watchpoints"],
|
||||
});
|
||||
|
||||
export const asyncStore = asyncStoreHelper("debugger", {
|
||||
|
|
|
@ -167,3 +167,5 @@ skip-if = (os == 'linux' && debug) || (os == 'linux' && asan) || ccov #Bug 1456
|
|||
[browser_dbg-bfcache.js]
|
||||
[browser_dbg-gc-breakpoint-positions.js]
|
||||
[browser_dbg-gc-sources.js]
|
||||
[browser_dbg-watchpoints.js]
|
||||
skip-if = debug
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/* 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/>. */
|
||||
|
||||
// Tests adding a watchpoint
|
||||
|
||||
add_task(async function() {
|
||||
pushPref("devtools.debugger.features.watchpoints", true);
|
||||
const dbg = await initDebugger("doc-sources.html");
|
||||
|
||||
await navigate(dbg, "doc-watchpoints.html", "doc-watchpoints.html");
|
||||
await selectSource(dbg, "doc-watchpoints.html");
|
||||
await waitForPaused(dbg);
|
||||
|
||||
info(`Add a get watchpoint at b`);
|
||||
await toggleScopeNode(dbg, 3);
|
||||
const addedWatchpoint = waitForDispatch(dbg, "SET_WATCHPOINT");
|
||||
await rightClickScopeNode(dbg, 5);
|
||||
selectContextMenuItem(dbg, selectors.addGetWatchpoint);
|
||||
await addedWatchpoint;
|
||||
|
||||
info(`Resume and wait to pause at the access to b on line 12`);
|
||||
resume(dbg);
|
||||
await waitForPaused(dbg);
|
||||
await waitForState(dbg, () => dbg.selectors.getSelectedInlinePreviews());
|
||||
assertPausedAtSourceAndLine(
|
||||
dbg,
|
||||
findSource(dbg, "doc-watchpoints.html").id,
|
||||
12
|
||||
);
|
||||
|
||||
const removedWatchpoint = waitForDispatch(dbg, "REMOVE_WATCHPOINT");
|
||||
await rightClickScopeNode(dbg, 5);
|
||||
selectContextMenuItem(dbg, selectors.removeWatchpoint);
|
||||
await removedWatchpoint;
|
||||
resume(dbg);
|
||||
await waitForRequestsToSettle(dbg);
|
||||
});
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
|
||||
<script>
|
||||
const obj = { a: { b: 2, c: 3 }, b: 2 };
|
||||
debugger
|
||||
obj.b = 3;
|
||||
|
||||
obj.a.b = 3;
|
||||
obj.a.b = 4;
|
||||
|
||||
console.log(obj.b);
|
||||
obj.b = 4;
|
||||
|
||||
debugger;
|
||||
</script>
|
||||
|
||||
<body>
|
||||
Hello World!
|
||||
</body>
|
||||
</html>
|
|
@ -419,7 +419,7 @@ function assertPausedAtSourceAndLine(dbg, expectedSourceId, expectedLine) {
|
|||
ok(frames.length >= 1, "Got at least one frame");
|
||||
const { sourceId, line } = frames[0].location;
|
||||
ok(sourceId == expectedSourceId, "Frame has correct source");
|
||||
ok(line == expectedLine, "Frame has correct line");
|
||||
ok(line == expectedLine, `Frame paused at ${line}, but expected ${expectedLine}`);
|
||||
}
|
||||
|
||||
// Get any workers associated with the debugger.
|
||||
|
@ -1291,6 +1291,9 @@ const selectors = {
|
|||
inlinePreviewLables: ".CodeMirror-linewidget .inline-preview-label",
|
||||
inlinePreviewValues: ".CodeMirror-linewidget .inline-preview-value",
|
||||
inlinePreviewOpenInspector: ".inline-preview-value button.open-inspector",
|
||||
addGetWatchpoint: "#node-menu-add-get-watchpoint",
|
||||
addSetWatchpoint: "#node-menu-add-set-watchpoint",
|
||||
removeWatchpoint: "#node-menu-remove-watchpoint"
|
||||
};
|
||||
|
||||
function getSelector(elementName, ...args) {
|
||||
|
@ -1391,6 +1394,7 @@ function rightClickElement(dbg, elementName, ...args) {
|
|||
|
||||
function rightClickEl(dbg, el) {
|
||||
const doc = dbg.win.document;
|
||||
el.scrollIntoView();
|
||||
EventUtils.synthesizeMouseAtCenter(el, { type: "contextmenu" }, dbg.win);
|
||||
}
|
||||
|
||||
|
@ -1443,6 +1447,10 @@ function toggleScopeNode(dbg, index) {
|
|||
return toggleObjectInspectorNode(findElement(dbg, "scopeNode", index));
|
||||
}
|
||||
|
||||
function rightClickScopeNode(dbg, index) {
|
||||
rightClickObjectInspectorNode(dbg, findElement(dbg, "scopeNode", index));
|
||||
}
|
||||
|
||||
function getScopeLabel(dbg, index) {
|
||||
return findElement(dbg, "scopeNode", index).innerText;
|
||||
}
|
||||
|
@ -1462,6 +1470,18 @@ function toggleObjectInspectorNode(node) {
|
|||
);
|
||||
}
|
||||
|
||||
function rightClickObjectInspectorNode(dbg, node) {
|
||||
const objectInspector = node.closest(".object-inspector");
|
||||
const properties = objectInspector.querySelectorAll(".node").length;
|
||||
|
||||
log(`Right clicking node ${node.innerText}`);
|
||||
rightClickEl(dbg, node);
|
||||
|
||||
return waitUntil(
|
||||
() => objectInspector.querySelectorAll(".node").length !== properties
|
||||
);
|
||||
}
|
||||
|
||||
function getCM(dbg) {
|
||||
const el = dbg.win.document.querySelector(".CodeMirror");
|
||||
return el.CodeMirror;
|
||||
|
|
|
@ -491,6 +491,10 @@ xhrBreakpoints.item.label=URL contains “%S”
|
|||
# when the debugger will pause on any XHR requests.
|
||||
pauseOnAnyXHR=Pause on any URL
|
||||
|
||||
# LOCALIZATION NOTE (watchpoints.removeWatchpoint): This is the text that appears in the
|
||||
# context menu to delete a watchpoint on an object property.
|
||||
watchpoints.removeWatchpoint=Remove watchpoint
|
||||
|
||||
# LOCALIZATION NOTE (sourceTabs.closeTab): Editor source tab context menu item
|
||||
# for closing the selected tab below the mouse.
|
||||
sourceTabs.closeTab=Close tab
|
||||
|
@ -776,6 +780,11 @@ whyPaused.xhr=Paused on XMLHttpRequest
|
|||
# promise rejection
|
||||
whyPaused.promiseRejection=Paused on promise rejection
|
||||
|
||||
# LOCALIZATION NOTE (whyPaused.getWatchpoint): The text that is displayed
|
||||
# in a info block explaining how the debugger is currently paused at a
|
||||
# watchpoint on an object property
|
||||
whyPaused.getWatchpoint=Paused on property access
|
||||
|
||||
# LOCALIZATION NOTE (whyPaused.assert): The text that is displayed
|
||||
# in a info block explaining how the debugger is currently paused on an
|
||||
# assert
|
||||
|
|
|
@ -86,3 +86,4 @@ pref("devtools.debugger.features.overlay-step-buttons", false);
|
|||
pref("devtools.debugger.features.overlay-step-buttons", true);
|
||||
pref("devtools.debugger.features.inline-preview", true);
|
||||
pref("devtools.debugger.features.log-event-breakpoints", false);
|
||||
pref("devtools.debugger.features.watchpoints", false);
|
||||
|
|
|
@ -458,3 +458,31 @@ button.invoke-getter {
|
|||
.tree-node.focused button.invoke-getter {
|
||||
background-color: currentColor;
|
||||
}
|
||||
|
||||
button.remove-set-watchpoint {
|
||||
mask: url("resource://devtools/client/debugger/images/webconsole-logpoint.svg")
|
||||
no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin: 0 4px 0px 20px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: var(--breakpoint-fill);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.remove-get-watchpoint {
|
||||
mask: url("resource://devtools/client/debugger/images/webconsole-logpoint.svg")
|
||||
no-repeat;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
height: 15px;
|
||||
width: 15px;
|
||||
margin: 0 4px 0px 20px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background-color: var(--purple-60);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -2321,6 +2321,16 @@ function getChildren(options) {
|
|||
}
|
||||
|
||||
return addToCache(makeNodesForProperties(loadedProps, item));
|
||||
} // Builds an expression that resolves to the value of the item in question
|
||||
// e.g. `b` in { a: { b: 2 } } resolves to `a.b`
|
||||
|
||||
|
||||
function getPathExpression(item) {
|
||||
if (item && item.parent) {
|
||||
return `${getPathExpression(item.parent)}.${item.name}`;
|
||||
}
|
||||
|
||||
return item.name;
|
||||
}
|
||||
|
||||
function getParent(item) {
|
||||
|
@ -2431,6 +2441,7 @@ module.exports = {
|
|||
getChildrenWithEvaluations,
|
||||
getClosestGripNode,
|
||||
getClosestNonBucketNode,
|
||||
getPathExpression,
|
||||
getParent,
|
||||
getParentGripValue,
|
||||
getNonPrototypeParentGripValue,
|
||||
|
@ -2484,12 +2495,14 @@ module.exports = {
|
|||
/* 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/>. */
|
||||
function initialState() {
|
||||
function initialState(overrides) {
|
||||
return {
|
||||
expandedPaths: new Set(),
|
||||
loadedProperties: new Map(),
|
||||
evaluations: new Map(),
|
||||
actors: new Set()
|
||||
actors: new Set(),
|
||||
watchpoints: new Map(),
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -2517,6 +2530,34 @@ function reducer(state = initialState(), action = {}) {
|
|||
});
|
||||
}
|
||||
|
||||
if (type == "SET_WATCHPOINT") {
|
||||
const {
|
||||
watchpoint,
|
||||
property,
|
||||
path
|
||||
} = data;
|
||||
const obj = state.loadedProperties.get(path);
|
||||
return cloneState({
|
||||
loadedProperties: new Map(state.loadedProperties).set(path, updateObject(obj, property, watchpoint)),
|
||||
watchpoints: new Map(state.watchpoints).set(data.actor, data.watchpoint)
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "REMOVE_WATCHPOINT") {
|
||||
const {
|
||||
path,
|
||||
property,
|
||||
actor
|
||||
} = data;
|
||||
const obj = state.loadedProperties.get(path);
|
||||
const watchpoints = new Map(state.watchpoints);
|
||||
watchpoints.delete(actor);
|
||||
return cloneState({
|
||||
loadedProperties: new Map(state.loadedProperties).set(path, updateObject(obj, property, null)),
|
||||
watchpoints: watchpoints
|
||||
});
|
||||
}
|
||||
|
||||
if (type === "NODE_PROPERTIES_LOADED") {
|
||||
return cloneState({
|
||||
actors: data.actor ? new Set(state.actors || []).add(data.actor) : state.actors,
|
||||
|
@ -2540,12 +2581,24 @@ function reducer(state = initialState(), action = {}) {
|
|||
|
||||
|
||||
if (type === "RESUME" || type == "NAVIGATE") {
|
||||
return initialState();
|
||||
return initialState({
|
||||
watchpoints: state.watchpoints
|
||||
});
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function updateObject(obj, property, watchpoint) {
|
||||
return { ...obj,
|
||||
ownProperties: { ...obj.ownProperties,
|
||||
[property]: { ...obj.ownProperties[property],
|
||||
watchpoint
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getObjectInspectorState(state) {
|
||||
return state.objectInspector;
|
||||
}
|
||||
|
@ -2562,6 +2615,10 @@ function getActors(state) {
|
|||
return getObjectInspectorState(state).actors;
|
||||
}
|
||||
|
||||
function getWatchpoints(state) {
|
||||
return getObjectInspectorState(state).watchpoints;
|
||||
}
|
||||
|
||||
function getLoadedProperties(state) {
|
||||
return getObjectInspectorState(state).loadedProperties;
|
||||
}
|
||||
|
@ -2576,6 +2633,7 @@ function getEvaluations(state) {
|
|||
|
||||
const selectors = {
|
||||
getActors,
|
||||
getWatchpoints,
|
||||
getEvaluations,
|
||||
getExpandedPathKeys,
|
||||
getExpandedPaths,
|
||||
|
@ -7395,9 +7453,12 @@ const utils = __webpack_require__(116);
|
|||
|
||||
const reducer = __webpack_require__(115);
|
||||
|
||||
const actions = __webpack_require__(485);
|
||||
|
||||
module.exports = {
|
||||
ObjectInspector,
|
||||
utils,
|
||||
actions,
|
||||
reducer
|
||||
};
|
||||
|
||||
|
@ -7521,6 +7582,12 @@ class ObjectInspector extends Component {
|
|||
if (this.roots !== nextProps.roots) {
|
||||
this.cachedNodes.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [path, properties] of nextProps.loadedProperties) {
|
||||
if (properties !== this.props.loadedProperties.get(path)) {
|
||||
this.cachedNodes.delete(path);
|
||||
}
|
||||
} // If there are new evaluations, we want to remove the existing cached
|
||||
// nodes from the cache.
|
||||
|
||||
|
@ -7549,7 +7616,7 @@ class ObjectInspector extends Component {
|
|||
// - OR the focused node changed.
|
||||
// - OR the active node changed.
|
||||
|
||||
return loadedProperties.size !== nextProps.loadedProperties.size || evaluations.size !== nextProps.evaluations.size || expandedPaths.size !== nextProps.expandedPaths.size && [...nextProps.expandedPaths].every(path => nextProps.loadedProperties.has(path)) || expandedPaths.size === nextProps.expandedPaths.size && [...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) || this.focusedItem !== nextProps.focusedItem || this.activeItem !== nextProps.activeItem || this.roots !== nextProps.roots;
|
||||
return loadedProperties !== nextProps.loadedProperties || loadedProperties.size !== nextProps.loadedProperties.size || evaluations.size !== nextProps.evaluations.size || expandedPaths.size !== nextProps.expandedPaths.size && [...nextProps.expandedPaths].every(path => nextProps.loadedProperties.has(path)) || expandedPaths.size === nextProps.expandedPaths.size && [...nextProps.expandedPaths].some(key => !expandedPaths.has(key)) || this.focusedItem !== nextProps.focusedItem || this.activeItem !== nextProps.activeItem || this.roots !== nextProps.roots;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -7746,9 +7813,15 @@ const {
|
|||
loadItemProperties
|
||||
} = __webpack_require__(196);
|
||||
|
||||
const {
|
||||
getPathExpression,
|
||||
getValue
|
||||
} = __webpack_require__(114);
|
||||
|
||||
const {
|
||||
getLoadedProperties,
|
||||
getActors
|
||||
getActors,
|
||||
getWatchpoints
|
||||
} = __webpack_require__(115);
|
||||
|
||||
/**
|
||||
|
@ -7817,6 +7890,67 @@ function nodePropertiesLoaded(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;
|
||||
const object = getValue(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 object = getValue(item.parent);
|
||||
const property = item.name;
|
||||
const path = item.parent.path;
|
||||
const actor = object.actor;
|
||||
await client.removeWatchpoint(object, property);
|
||||
dispatch({
|
||||
type: "REMOVE_WATCHPOINT",
|
||||
data: {
|
||||
path,
|
||||
property,
|
||||
actor
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function closeObjectInspector() {
|
||||
return async ({
|
||||
|
@ -7852,9 +7986,14 @@ function rootsChanged(props) {
|
|||
|
||||
function releaseActors(state, client) {
|
||||
const actors = getActors(state);
|
||||
const watchpoints = getWatchpoints(state);
|
||||
|
||||
for (const actor of actors) {
|
||||
client.releaseActor(actor);
|
||||
// Watchpoints are stored in object actors.
|
||||
// If we release the actor we lose the watchpoint.
|
||||
if (!watchpoints.has(actor)) {
|
||||
client.releaseActor(actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7887,7 +8026,9 @@ module.exports = {
|
|||
nodeCollapse,
|
||||
nodeLoadProperties,
|
||||
nodePropertiesLoaded,
|
||||
rootsChanged
|
||||
rootsChanged,
|
||||
addWatchpoint,
|
||||
removeWatchpoint
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
|
@ -7957,7 +8098,13 @@ const {
|
|||
} = Utils.node;
|
||||
|
||||
class ObjectInspectorItem extends Component {
|
||||
// eslint-disable-next-line complexity
|
||||
static get defaultProps() {
|
||||
return {
|
||||
onContextMenu: () => {}
|
||||
};
|
||||
} // eslint-disable-next-line complexity
|
||||
|
||||
|
||||
getLabelAndValue() {
|
||||
const {
|
||||
item,
|
||||
|
@ -8072,7 +8219,8 @@ class ObjectInspectorItem extends Component {
|
|||
expanded,
|
||||
onCmdCtrlClick,
|
||||
onDoubleClick,
|
||||
dimTopLevelWindow
|
||||
dimTopLevelWindow,
|
||||
onContextMenu
|
||||
} = this.props;
|
||||
const parentElementProps = {
|
||||
className: classnames("node object-node", {
|
||||
|
@ -8101,7 +8249,8 @@ class ObjectInspectorItem extends Component {
|
|||
if (Utils.selection.documentHasSelection() && !(e.target && e.target.matches && e.target.matches(".arrow"))) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
}
|
||||
},
|
||||
onContextMenu: e => onContextMenu(e, item)
|
||||
};
|
||||
|
||||
if (onDoubleClick) {
|
||||
|
@ -8149,6 +8298,24 @@ class ObjectInspectorItem extends Component {
|
|||
}, label);
|
||||
}
|
||||
|
||||
renderWatchpointButton() {
|
||||
const {
|
||||
item,
|
||||
removeWatchpoint
|
||||
} = this.props;
|
||||
|
||||
if (!item || !item.contents || !item.contents.watchpoint) {
|
||||
return;
|
||||
}
|
||||
|
||||
const watchpoint = item.contents.watchpoint;
|
||||
return dom.button({
|
||||
className: `remove-${watchpoint}-watchpoint`,
|
||||
title: L10N.getStr("watchpoints.removeWatchpoint"),
|
||||
onClick: () => removeWatchpoint(item)
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
arrow
|
||||
|
@ -8161,7 +8328,7 @@ class ObjectInspectorItem extends Component {
|
|||
const delimiter = value && labelElement ? dom.span({
|
||||
className: "object-delimiter"
|
||||
}, ": ") : null;
|
||||
return dom.div(this.getTreeItemProps(), arrow, labelElement, delimiter, value);
|
||||
return dom.div(this.getTreeItemProps(), arrow, labelElement, delimiter, value, this.renderWatchpointButton());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -123,10 +123,10 @@ const proto = {
|
|||
|
||||
this._originalDescriptors.set(property, { desc, watchpointType });
|
||||
|
||||
const pauseAndRespond = () => {
|
||||
const pauseAndRespond = type => {
|
||||
const frame = this.thread.dbg.getNewestFrame();
|
||||
this.thread._pauseAndRespond(frame, {
|
||||
type: "watchpoint",
|
||||
type: type,
|
||||
message: label,
|
||||
});
|
||||
};
|
||||
|
@ -139,7 +139,7 @@ const proto = {
|
|||
desc.value = v;
|
||||
}),
|
||||
get: this.obj.makeDebuggeeValue(() => {
|
||||
pauseAndRespond();
|
||||
pauseAndRespond("getWatchpoint");
|
||||
return desc.value;
|
||||
}),
|
||||
});
|
||||
|
@ -150,7 +150,7 @@ const proto = {
|
|||
configurable: desc.configurable,
|
||||
enumerable: desc.enumerable,
|
||||
set: this.obj.makeDebuggeeValue(v => {
|
||||
pauseAndRespond();
|
||||
pauseAndRespond("setWatchpoint");
|
||||
desc.value = v;
|
||||
}),
|
||||
get: this.obj.makeDebuggeeValue(v => {
|
||||
|
|
|
@ -180,6 +180,8 @@ RootActor.prototype = {
|
|||
// Supports native log points and modifying the condition/log of an existing
|
||||
// breakpoints. Fx66+
|
||||
nativeLogpoints: true,
|
||||
// Supports watchpoints in the server for Fx71+
|
||||
watchpoints: true,
|
||||
// support older browsers for Fx69+
|
||||
hasThreadFront: true,
|
||||
},
|
||||
|
|
|
@ -51,7 +51,7 @@ async function testSetWatchpoint({ threadFront, debuggee }) {
|
|||
//Test that watchpoint triggers pause on set.
|
||||
const packet2 = await resumeAndWaitForPause(threadFront);
|
||||
Assert.equal(packet2.frame.where.line, 4);
|
||||
Assert.equal(packet2.why.type, "watchpoint");
|
||||
Assert.equal(packet2.why.type, "setWatchpoint");
|
||||
Assert.equal(obj.preview.ownProperties.a.value, 1);
|
||||
|
||||
await resume(threadFront);
|
||||
|
@ -91,7 +91,7 @@ async function testGetWatchpoint({ threadFront, debuggee }) {
|
|||
//Test that watchpoint triggers pause on get.
|
||||
const packet2 = await resumeAndWaitForPause(threadFront);
|
||||
Assert.equal(packet2.frame.where.line, 4);
|
||||
Assert.equal(packet2.why.type, "watchpoint");
|
||||
Assert.equal(packet2.why.type, "getWatchpoint");
|
||||
Assert.equal(obj.preview.ownProperties.a.value, 1);
|
||||
|
||||
await resume(threadFront);
|
||||
|
|
Загрузка…
Ссылка в новой задаче