diff options
Diffstat (limited to 'devtools/server/actors/utils/watchpoint-map.js')
-rw-r--r-- | devtools/server/actors/utils/watchpoint-map.js | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/devtools/server/actors/utils/watchpoint-map.js b/devtools/server/actors/utils/watchpoint-map.js new file mode 100644 index 0000000000..b1a2dd75c9 --- /dev/null +++ b/devtools/server/actors/utils/watchpoint-map.js @@ -0,0 +1,163 @@ +/* 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"; + +class WatchpointMap { + constructor(threadActor) { + this.threadActor = threadActor; + this._watchpoints = new Map(); + } + + _setWatchpoint(objActor, data) { + const { property, label, watchpointType } = data; + const obj = objActor.rawValue(); + + const desc = objActor.obj.getOwnPropertyDescriptor(property); + + if (this.has(obj, property) || desc.set || desc.get || !desc.configurable) { + return null; + } + + function getValue() { + return typeof desc.value === "object" && desc.value + ? desc.value.unsafeDereference() + : desc.value; + } + + function setValue(v) { + desc.value = objActor.obj.makeDebuggeeValue(v); + } + + const maybeHandlePause = type => { + const frame = this.threadActor.dbg.getNewestFrame(); + + if ( + !this.threadActor.hasMoved(frame, type) || + this.threadActor.skipBreakpointsOption || + this.threadActor.sourcesManager.isFrameBlackBoxed(frame) + ) { + return; + } + + this.threadActor._pauseAndRespond(frame, { + type, + message: label, + }); + }; + + if (watchpointType === "get") { + objActor.obj.defineProperty(property, { + configurable: desc.configurable, + enumerable: desc.enumerable, + set: objActor.obj.makeDebuggeeValue(v => { + setValue(v); + }), + get: objActor.obj.makeDebuggeeValue(() => { + maybeHandlePause("getWatchpoint"); + return getValue(); + }), + }); + } + + if (watchpointType === "set") { + objActor.obj.defineProperty(property, { + configurable: desc.configurable, + enumerable: desc.enumerable, + set: objActor.obj.makeDebuggeeValue(v => { + maybeHandlePause("setWatchpoint"); + setValue(v); + }), + get: objActor.obj.makeDebuggeeValue(() => { + return getValue(); + }), + }); + } + + if (watchpointType === "getorset") { + objActor.obj.defineProperty(property, { + configurable: desc.configurable, + enumerable: desc.enumerable, + set: objActor.obj.makeDebuggeeValue(v => { + maybeHandlePause("setWatchpoint"); + setValue(v); + }), + get: objActor.obj.makeDebuggeeValue(() => { + maybeHandlePause("getWatchpoint"); + return getValue(); + }), + }); + } + + return desc; + } + + add(objActor, data) { + // Get the object's description before calling setWatchpoint, + // otherwise we'll get the modified property descriptor instead + const desc = this._setWatchpoint(objActor, data); + if (!desc) { + return; + } + + const objWatchpoints = + this._watchpoints.get(objActor.rawValue()) || new Map(); + + objWatchpoints.set(data.property, { ...data, desc }); + this._watchpoints.set(objActor.rawValue(), objWatchpoints); + } + + has(obj, property) { + const objWatchpoints = this._watchpoints.get(obj); + return objWatchpoints && objWatchpoints.has(property); + } + + get(obj, property) { + const objWatchpoints = this._watchpoints.get(obj); + return objWatchpoints && objWatchpoints.get(property); + } + + remove(objActor, property) { + const obj = objActor.rawValue(); + + // This should remove watchpoints on all of the object's properties if + // a property isn't passed in as an argument + if (!property) { + for (const objProperty in obj) { + this.remove(objActor, objProperty); + } + } + + if (!this.has(obj, property)) { + return; + } + + const objWatchpoints = this._watchpoints.get(obj); + const { desc } = objWatchpoints.get(property); + + objWatchpoints.delete(property); + this._watchpoints.set(obj, objWatchpoints); + + // We should stop keeping track of an object if it no longer + // has a watchpoint + if (objWatchpoints.size == 0) { + this._watchpoints.delete(obj); + } + + objActor.obj.defineProperty(property, desc); + } + + removeAll(objActor) { + const objWatchpoints = this._watchpoints.get(objActor.rawValue()); + if (!objWatchpoints) { + return; + } + + for (const objProperty in objWatchpoints) { + this.remove(objActor, objProperty); + } + } +} + +exports.WatchpointMap = WatchpointMap; |