blob: b6aaac23ce13d43d4815236a1a51420a74877208 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @implements {SDK.TargetManager.Observer}
* @unrestricted
*/
Bindings.BreakpointManager = class extends Common.Object {
/**
* @param {?Common.Setting} breakpointsSetting
* @param {!Workspace.Workspace} workspace
* @param {!SDK.TargetManager} targetManager
* @param {!Bindings.DebuggerWorkspaceBinding} debuggerWorkspaceBinding
*/
constructor(breakpointsSetting, workspace, targetManager, debuggerWorkspaceBinding) {
super();
this._storage = new Bindings.BreakpointManager.Storage(this, breakpointsSetting);
this._workspace = workspace;
this._targetManager = targetManager;
this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
this._breakpointsActive = true;
/** @type {!Map<!Workspace.UISourceCode, !Map<number, !Map<number, !Array<!Bindings.BreakpointManager.Breakpoint>>>>} */
this._breakpointsForUISourceCode = new Map();
/** @type {!Map<!Workspace.UISourceCode, !Array<!Bindings.BreakpointManager.Breakpoint>>} */
this._breakpointsForPrimaryUISourceCode = new Map();
/** @type {!Multimap.<string, !Bindings.BreakpointManager.Breakpoint>} */
this._provisionalBreakpoints = new Multimap();
this._workspace.addEventListener(Workspace.Workspace.Events.ProjectRemoved, this._projectRemoved, this);
this._workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeAdded, this._uiSourceCodeAdded, this);
this._workspace.addEventListener(Workspace.Workspace.Events.UISourceCodeRemoved, this._uiSourceCodeRemoved, this);
targetManager.observeTargets(this, SDK.Target.Capability.JS);
}
/**
* @param {string} sourceFileId
* @param {number} lineNumber
* @param {number} columnNumber
* @return {string}
*/
static _breakpointStorageId(sourceFileId, lineNumber, columnNumber) {
if (!sourceFileId)
return '';
return sourceFileId + ':' + lineNumber + ':' + columnNumber;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {string}
*/
_sourceFileId(uiSourceCode) {
// TODO(lushnikov): _sourceFileId is not needed any more.
return uiSourceCode.url();
}
/**
* @override
* @param {!SDK.Target} target
*/
targetAdded(target) {
var debuggerModel = SDK.DebuggerModel.fromTarget(target);
if (debuggerModel && !this._breakpointsActive)
debuggerModel.setBreakpointsActive(this._breakpointsActive);
}
/**
* @override
* @param {!SDK.Target} target
*/
targetRemoved(target) {
}
/**
* @param {string} sourceFileId
* @return {!Map.<string, !Bindings.BreakpointManager.Breakpoint>}
*/
_provisionalBreakpointsForSourceFileId(sourceFileId) {
var result = new Map();
var breakpoints = this._provisionalBreakpoints.get(sourceFileId).valuesArray();
for (var i = 0; i < breakpoints.length; ++i)
result.set(breakpoints[i]._breakpointStorageId(), breakpoints[i]);
return result;
}
removeProvisionalBreakpointsForTest() {
var breakpoints = this._provisionalBreakpoints.valuesArray();
for (var i = 0; i < breakpoints.length; ++i)
breakpoints[i].remove();
this._provisionalBreakpoints.clear();
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_restoreBreakpoints(uiSourceCode) {
var sourceFileId = this._sourceFileId(uiSourceCode);
if (!sourceFileId)
return;
this._storage.mute();
var breakpointItems = this._storage.breakpointItems(this._sourceFileId(uiSourceCode));
var provisionalBreakpoints = this._provisionalBreakpointsForSourceFileId(sourceFileId);
for (var i = 0; i < breakpointItems.length; ++i) {
var breakpointItem = breakpointItems[i];
var itemStorageId = Bindings.BreakpointManager._breakpointStorageId(
breakpointItem.sourceFileId, breakpointItem.lineNumber, breakpointItem.columnNumber);
var provisionalBreakpoint = provisionalBreakpoints.get(itemStorageId);
if (provisionalBreakpoint) {
if (!this._breakpointsForPrimaryUISourceCode.get(uiSourceCode))
this._breakpointsForPrimaryUISourceCode.set(uiSourceCode, []);
this._breakpointsForPrimaryUISourceCode.get(uiSourceCode).push(provisionalBreakpoint);
provisionalBreakpoint._updateBreakpoint();
} else {
this._innerSetBreakpoint(
uiSourceCode, breakpointItem.lineNumber, breakpointItem.columnNumber, breakpointItem.condition,
breakpointItem.enabled);
}
}
this._provisionalBreakpoints.removeAll(sourceFileId);
this._storage.unmute();
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeAdded(event) {
var uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data);
this._restoreBreakpoints(uiSourceCode);
if (uiSourceCode.contentType().hasScripts()) {
uiSourceCode.addEventListener(
Workspace.UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this);
}
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeRemoved(event) {
var uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.data);
this._removeUISourceCode(uiSourceCode);
}
/**
* @param {!Common.Event} event
*/
_uiSourceCodeMappingChanged(event) {
var uiSourceCode = /** @type {!Workspace.UISourceCode} */ (event.target);
var isIdentity = /** @type {boolean} */ (event.data.isIdentity);
var target = /** @type {!SDK.Target} */ (event.data.target);
if (isIdentity)
return;
var breakpoints = this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || [];
for (var i = 0; i < breakpoints.length; ++i)
breakpoints[i]._updateInDebuggerForTarget(target);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
*/
_removeUISourceCode(uiSourceCode) {
var breakpoints = this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || [];
var sourceFileId = this._sourceFileId(uiSourceCode);
for (var i = 0; i < breakpoints.length; ++i) {
breakpoints[i]._resetLocations();
if (breakpoints[i].enabled())
this._provisionalBreakpoints.set(sourceFileId, breakpoints[i]);
}
uiSourceCode.removeEventListener(
Workspace.UISourceCode.Events.SourceMappingChanged, this._uiSourceCodeMappingChanged, this);
this._breakpointsForPrimaryUISourceCode.remove(uiSourceCode);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
* @param {string} condition
* @param {boolean} enabled
* @return {!Bindings.BreakpointManager.Breakpoint}
*/
setBreakpoint(uiSourceCode, lineNumber, columnNumber, condition, enabled) {
var uiLocation = new Workspace.UILocation(uiSourceCode, lineNumber, columnNumber);
var normalizedLocation = this._debuggerWorkspaceBinding.normalizeUILocation(uiLocation);
if (normalizedLocation.id() !== uiLocation.id()) {
Common.Revealer.reveal(normalizedLocation);
uiLocation = normalizedLocation;
}
this.setBreakpointsActive(true);
return this._innerSetBreakpoint(
uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber, condition, enabled);
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
* @param {string} condition
* @param {boolean} enabled
* @return {!Bindings.BreakpointManager.Breakpoint}
*/
_innerSetBreakpoint(uiSourceCode, lineNumber, columnNumber, condition, enabled) {
var breakpoint = this.findBreakpoint(uiSourceCode, lineNumber, columnNumber);
if (breakpoint) {
breakpoint._updateState(condition, enabled);
return breakpoint;
}
var projectId = uiSourceCode.project().id();
var path = uiSourceCode.url();
var sourceFileId = this._sourceFileId(uiSourceCode);
breakpoint = new Bindings.BreakpointManager.Breakpoint(
this, projectId, path, sourceFileId, lineNumber, columnNumber, condition, enabled);
if (!this._breakpointsForPrimaryUISourceCode.get(uiSourceCode))
this._breakpointsForPrimaryUISourceCode.set(uiSourceCode, []);
this._breakpointsForPrimaryUISourceCode.get(uiSourceCode).push(breakpoint);
return breakpoint;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @return {!Array<!Bindings.BreakpointManager.Breakpoint>}
*/
findBreakpoints(uiSourceCode, lineNumber) {
var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
var lineBreakpoints = breakpoints ? breakpoints.get(lineNumber) : null;
return lineBreakpoints ? lineBreakpoints.valuesArray()[0] : [];
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {number} lineNumber
* @param {number} columnNumber
* @return {?Bindings.BreakpointManager.Breakpoint}
*/
findBreakpoint(uiSourceCode, lineNumber, columnNumber) {
var breakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
var lineBreakpoints = breakpoints ? breakpoints.get(lineNumber) : null;
var columnBreakpoints = lineBreakpoints ? lineBreakpoints.get(columnNumber) : null;
return columnBreakpoints ? columnBreakpoints[0] : null;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @param {!Common.TextRange} textRange
* @return {!Promise<!Array<!Workspace.UILocation>>}
*/
possibleBreakpoints(uiSourceCode, textRange) {
var targets = this._targetManager.targets(SDK.Target.Capability.JS);
if (!targets.length)
return Promise.resolve([]);
for (var target of targets) {
var startLocation = this._debuggerWorkspaceBinding.uiLocationToRawLocation(
target, uiSourceCode, textRange.startLine, textRange.startColumn);
if (!startLocation)
continue;
var endLocation = this._debuggerWorkspaceBinding.uiLocationToRawLocation(
target, uiSourceCode, textRange.endLine, textRange.endColumn);
if (!endLocation)
continue;
var debuggerModel = SDK.DebuggerModel.fromTarget(target);
return debuggerModel.getPossibleBreakpoints(startLocation, endLocation).then(toUILocations.bind(this));
}
return Promise.resolve([]);
/**
* @this {!Bindings.BreakpointManager}
* @param {!Array<!SDK.DebuggerModel.Location>} locations
* @return {!Array<!Workspace.UILocation>}
*/
function toUILocations(locations) {
var sortedLocations = locations.map(location => this._debuggerWorkspaceBinding.rawLocationToUILocation(location));
sortedLocations = sortedLocations.filter(location => location && location.uiSourceCode === uiSourceCode);
sortedLocations.sort(Workspace.UILocation.comparator);
if (!sortedLocations.length)
return [];
var result = [sortedLocations[0]];
var lastLocation = sortedLocations[0];
for (var i = 1; i < sortedLocations.length; ++i) {
if (sortedLocations[i].id() === lastLocation.id())
continue;
result.push(sortedLocations[i]);
lastLocation = sortedLocations[i];
}
return result;
}
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {!Array.<!Bindings.BreakpointManager.Breakpoint>}
*/
breakpointsForUISourceCode(uiSourceCode) {
var result = [];
var uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
var breakpoints = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.valuesArray() : [];
for (var i = 0; i < breakpoints.length; ++i) {
var lineBreakpoints = breakpoints[i];
var columnBreakpointArrays = lineBreakpoints ? lineBreakpoints.valuesArray() : [];
result = result.concat.apply(result, columnBreakpointArrays);
}
return result;
}
/**
* @return {!Array.<!Bindings.BreakpointManager.Breakpoint>}
*/
allBreakpoints() {
var result = [];
var uiSourceCodes = this._breakpointsForUISourceCode.keysArray();
for (var i = 0; i < uiSourceCodes.length; ++i)
result = result.concat(this.breakpointsForUISourceCode(uiSourceCodes[i]));
return result;
}
/**
* @param {!Workspace.UISourceCode} uiSourceCode
* @return {!Array.<!{breakpoint: !Bindings.BreakpointManager.Breakpoint, uiLocation: !Workspace.UILocation}>}
*/
breakpointLocationsForUISourceCode(uiSourceCode) {
var uiSourceCodeBreakpoints = this._breakpointsForUISourceCode.get(uiSourceCode);
var lineNumbers = uiSourceCodeBreakpoints ? uiSourceCodeBreakpoints.keysArray() : [];
var result = [];
for (var i = 0; i < lineNumbers.length; ++i) {
var lineBreakpoints = uiSourceCodeBreakpoints.get(lineNumbers[i]);
var columnNumbers = lineBreakpoints.keysArray();
for (var j = 0; j < columnNumbers.length; ++j) {
var columnBreakpoints = lineBreakpoints.get(columnNumbers[j]);
var lineNumber = parseInt(lineNumbers[i], 10);
var columnNumber = parseInt(columnNumbers[j], 10);
for (var k = 0; k < columnBreakpoints.length; ++k) {
var breakpoint = columnBreakpoints[k];
var uiLocation = uiSourceCode.uiLocation(lineNumber, columnNumber);
result.push({breakpoint: breakpoint, uiLocation: uiLocation});
}
}
}
return result;
}
/**
* @return {!Array.<!{breakpoint: !Bindings.BreakpointManager.Breakpoint, uiLocation: !Workspace.UILocation}>}
*/
allBreakpointLocations() {
var result = [];
var uiSourceCodes = this._breakpointsForUISourceCode.keysArray();
for (var i = 0; i < uiSourceCodes.length; ++i)
result = result.concat(this.breakpointLocationsForUISourceCode(uiSourceCodes[i]));
return result;
}
/**
* @param {boolean} toggleState
*/
toggleAllBreakpoints(toggleState) {
var breakpoints = this.allBreakpoints();
for (var i = 0; i < breakpoints.length; ++i)
breakpoints[i].setEnabled(toggleState);
}
removeAllBreakpoints() {
var breakpoints = this.allBreakpoints();
for (var i = 0; i < breakpoints.length; ++i)
breakpoints[i].remove();
}
_projectRemoved(event) {
var project = /** @type {!Workspace.Project} */ (event.data);
var uiSourceCodes = project.uiSourceCodes();
for (var i = 0; i < uiSourceCodes.length; ++i)
this._removeUISourceCode(uiSourceCodes[i]);
}
/**
* @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
* @param {boolean} removeFromStorage
*/
_removeBreakpoint(breakpoint, removeFromStorage) {
var uiSourceCode = breakpoint.uiSourceCode();
var breakpoints = uiSourceCode ? this._breakpointsForPrimaryUISourceCode.get(uiSourceCode) || [] : [];
breakpoints.remove(breakpoint);
if (removeFromStorage)
this._storage._removeBreakpoint(breakpoint);
this._provisionalBreakpoints.remove(breakpoint._sourceFileId, breakpoint);
}
/**
* @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
* @param {!Workspace.UILocation} uiLocation
*/
_uiLocationAdded(breakpoint, uiLocation) {
var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode);
if (!breakpoints) {
breakpoints = new Map();
this._breakpointsForUISourceCode.set(uiLocation.uiSourceCode, breakpoints);
}
var lineBreakpoints = breakpoints.get(uiLocation.lineNumber);
if (!lineBreakpoints) {
lineBreakpoints = new Map();
breakpoints.set(uiLocation.lineNumber, lineBreakpoints);
}
var columnBreakpoints = lineBreakpoints.get(uiLocation.columnNumber);
if (!columnBreakpoints) {
columnBreakpoints = [];
lineBreakpoints.set(uiLocation.columnNumber, columnBreakpoints);
}
columnBreakpoints.push(breakpoint);
this.dispatchEventToListeners(
Bindings.BreakpointManager.Events.BreakpointAdded, {breakpoint: breakpoint, uiLocation: uiLocation});
}
/**
* @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
* @param {!Workspace.UILocation} uiLocation
*/
_uiLocationRemoved(breakpoint, uiLocation) {
var breakpoints = this._breakpointsForUISourceCode.get(uiLocation.uiSourceCode);
if (!breakpoints)
return;
var lineBreakpoints = breakpoints.get(uiLocation.lineNumber);
if (!lineBreakpoints)
return;
var columnBreakpoints = lineBreakpoints.get(uiLocation.columnNumber);
if (!columnBreakpoints)
return;
columnBreakpoints.remove(breakpoint);
if (!columnBreakpoints.length)
lineBreakpoints.remove(uiLocation.columnNumber);
if (!lineBreakpoints.size)
breakpoints.remove(uiLocation.lineNumber);
if (!breakpoints.size)
this._breakpointsForUISourceCode.remove(uiLocation.uiSourceCode);
this.dispatchEventToListeners(
Bindings.BreakpointManager.Events.BreakpointRemoved, {breakpoint: breakpoint, uiLocation: uiLocation});
}
/**
* @param {boolean} active
*/
setBreakpointsActive(active) {
if (this._breakpointsActive === active)
return;
this._breakpointsActive = active;
var debuggerModels = SDK.DebuggerModel.instances();
for (var i = 0; i < debuggerModels.length; ++i)
debuggerModels[i].setBreakpointsActive(active);
this.dispatchEventToListeners(Bindings.BreakpointManager.Events.BreakpointsActiveStateChanged, active);
}
/**
* @return {boolean}
*/
breakpointsActive() {
return this._breakpointsActive;
}
};
/** @enum {symbol} */
Bindings.BreakpointManager.Events = {
BreakpointAdded: Symbol('breakpoint-added'),
BreakpointRemoved: Symbol('breakpoint-removed'),
BreakpointsActiveStateChanged: Symbol('BreakpointsActiveStateChanged')
};
/**
* @implements {SDK.TargetManager.Observer}
* @unrestricted
*/
Bindings.BreakpointManager.Breakpoint = class {
/**
* @param {!Bindings.BreakpointManager} breakpointManager
* @param {string} projectId
* @param {string} path
* @param {string} sourceFileId
* @param {number} lineNumber
* @param {number} columnNumber
* @param {string} condition
* @param {boolean} enabled
*/
constructor(breakpointManager, projectId, path, sourceFileId, lineNumber, columnNumber, condition, enabled) {
this._breakpointManager = breakpointManager;
this._projectId = projectId;
this._path = path;
this._lineNumber = lineNumber;
this._columnNumber = columnNumber;
this._sourceFileId = sourceFileId;
/** @type {!Map<string, number>} */
this._numberOfDebuggerLocationForUILocation = new Map();
// Force breakpoint update.
/** @type {string} */ this._condition;
/** @type {boolean} */ this._enabled;
/** @type {boolean} */ this._isRemoved;
/** @type {!Workspace.UILocation|undefined} */ this._fakePrimaryLocation;
this._currentState = null;
/** @type {!Map.<!SDK.Target, !Bindings.BreakpointManager.TargetBreakpoint>}*/
this._targetBreakpoints = new Map();
this._updateState(condition, enabled);
this._breakpointManager._targetManager.observeTargets(this);
}
/**
* @override
* @param {!SDK.Target} target
*/
targetAdded(target) {
var debuggerModel = SDK.DebuggerModel.fromTarget(target);
if (!debuggerModel)
return;
var debuggerWorkspaceBinding = this._breakpointManager._debuggerWorkspaceBinding;
this._targetBreakpoints.set(
target, new Bindings.BreakpointManager.TargetBreakpoint(debuggerModel, this, debuggerWorkspaceBinding));
}
/**
* @override
* @param {!SDK.Target} target
*/
targetRemoved(target) {
var debuggerModel = SDK.DebuggerModel.fromTarget(target);
if (!debuggerModel)
return;
var targetBreakpoint = this._targetBreakpoints.remove(target);
targetBreakpoint._cleanUpAfterDebuggerIsGone();
targetBreakpoint._removeEventListeners();
}
/**
* @return {string}
*/
projectId() {
return this._projectId;
}
/**
* @return {string}
*/
path() {
return this._path;
}
/**
* @return {number}
*/
lineNumber() {
return this._lineNumber;
}
/**
* @return {number}
*/
columnNumber() {
return this._columnNumber;
}
/**
* @return {?Workspace.UISourceCode}
*/
uiSourceCode() {
return this._breakpointManager._workspace.uiSourceCode(this._projectId, this._path);
}
/**
* @param {?Workspace.UILocation} oldUILocation
* @param {!Workspace.UILocation} newUILocation
*/
_replaceUILocation(oldUILocation, newUILocation) {
if (this._isRemoved)
return;
this._removeUILocation(oldUILocation, true);
this._removeFakeBreakpointAtPrimaryLocation();
var current = (this._numberOfDebuggerLocationForUILocation.get(newUILocation.id()) || 0) + 1;
this._numberOfDebuggerLocationForUILocation.set(newUILocation.id(), current);
if (current === 1)
this._breakpointManager._uiLocationAdded(this, newUILocation);
}
/**
* @param {?Workspace.UILocation} uiLocation
* @param {boolean=} muteCreationFakeBreakpoint
*/
_removeUILocation(uiLocation, muteCreationFakeBreakpoint) {
if (!uiLocation || !this._numberOfDebuggerLocationForUILocation.has(uiLocation.id()))
return;
var current = (this._numberOfDebuggerLocationForUILocation.get(uiLocation.id()) || 0) - 1;
this._numberOfDebuggerLocationForUILocation.set(uiLocation.id(), current);
if (current !== 0)
return;
this._numberOfDebuggerLocationForUILocation.delete(uiLocation.id());
this._breakpointManager._uiLocationRemoved(this, uiLocation);
if (!muteCreationFakeBreakpoint)
this._fakeBreakpointAtPrimaryLocation();
}
/**
* @return {boolean}
*/
enabled() {
return this._enabled;
}
/**
* @param {boolean} enabled
*/
setEnabled(enabled) {
this._updateState(this._condition, enabled);
}
/**
* @return {string}
*/
condition() {
return this._condition;
}
/**
* @param {string} condition
*/
setCondition(condition) {
this._updateState(condition, this._enabled);
}
/**
* @param {string} condition
* @param {boolean} enabled
*/
_updateState(condition, enabled) {
if (this._enabled === enabled && this._condition === condition)
return;
this._enabled = enabled;
this._condition = condition;
this._breakpointManager._storage._updateBreakpoint(this);
this._updateBreakpoint();
}
_updateBreakpoint() {
this._removeFakeBreakpointAtPrimaryLocation();
this._fakeBreakpointAtPrimaryLocation();
var targetBreakpoints = this._targetBreakpoints.valuesArray();
for (var i = 0; i < targetBreakpoints.length; ++i)
targetBreakpoints[i]._scheduleUpdateInDebugger();
}
/**
* @param {boolean=} keepInStorage
*/
remove(keepInStorage) {
this._isRemoved = true;
var removeFromStorage = !keepInStorage;
this._removeFakeBreakpointAtPrimaryLocation();
var targetBreakpoints = this._targetBreakpoints.valuesArray();
for (var i = 0; i < targetBreakpoints.length; ++i) {
targetBreakpoints[i]._scheduleUpdateInDebugger();
targetBreakpoints[i]._removeEventListeners();
}
this._breakpointManager._removeBreakpoint(this, removeFromStorage);
this._breakpointManager._targetManager.unobserveTargets(this);
}
/**
* @param {!SDK.Target} target
*/
_updateInDebuggerForTarget(target) {
this._targetBreakpoints.get(target)._scheduleUpdateInDebugger();
}
/**
* @return {string}
*/
_breakpointStorageId() {
return Bindings.BreakpointManager._breakpointStorageId(this._sourceFileId, this._lineNumber, this._columnNumber);
}
_fakeBreakpointAtPrimaryLocation() {
if (this._isRemoved || this._numberOfDebuggerLocationForUILocation.size || this._fakePrimaryLocation)
return;
var uiSourceCode = this._breakpointManager._workspace.uiSourceCode(this._projectId, this._path);
if (!uiSourceCode)
return;
this._fakePrimaryLocation = uiSourceCode.uiLocation(this._lineNumber, this._columnNumber);
if (this._fakePrimaryLocation)
this._breakpointManager._uiLocationAdded(this, this._fakePrimaryLocation);
}
_removeFakeBreakpointAtPrimaryLocation() {
if (this._fakePrimaryLocation) {
this._breakpointManager._uiLocationRemoved(this, this._fakePrimaryLocation);
delete this._fakePrimaryLocation;
}
}
_resetLocations() {
this._removeFakeBreakpointAtPrimaryLocation();
var targetBreakpoints = this._targetBreakpoints.valuesArray();
for (var i = 0; i < targetBreakpoints.length; ++i)
targetBreakpoints[i]._resetLocations();
}
};
/**
* @unrestricted
*/
Bindings.BreakpointManager.TargetBreakpoint = class extends SDK.SDKObject {
/**
* @param {!SDK.DebuggerModel} debuggerModel
* @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
* @param {!Bindings.DebuggerWorkspaceBinding} debuggerWorkspaceBinding
*/
constructor(debuggerModel, breakpoint, debuggerWorkspaceBinding) {
super(debuggerModel.target());
this._debuggerModel = debuggerModel;
this._breakpoint = breakpoint;
this._debuggerWorkspaceBinding = debuggerWorkspaceBinding;
this._liveLocations = new Bindings.LiveLocationPool();
/** @type {!Map<string, !Workspace.UILocation>} */
this._uiLocations = new Map();
this._debuggerModel.addEventListener(
SDK.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this);
this._debuggerModel.addEventListener(
SDK.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this);
this._hasPendingUpdate = false;
this._isUpdating = false;
this._cancelCallback = false;
this._currentState = null;
if (this._debuggerModel.debuggerEnabled())
this._scheduleUpdateInDebugger();
}
_resetLocations() {
for (var uiLocation of this._uiLocations.values())
this._breakpoint._removeUILocation(uiLocation);
this._uiLocations.clear();
this._liveLocations.disposeAll();
}
_scheduleUpdateInDebugger() {
if (this._isUpdating) {
this._hasPendingUpdate = true;
return;
}
this._isUpdating = true;
this._updateInDebugger(this._didUpdateInDebugger.bind(this));
}
_didUpdateInDebugger() {
this._isUpdating = false;
if (this._hasPendingUpdate) {
this._hasPendingUpdate = false;
this._scheduleUpdateInDebugger();
}
}
/**
* @return {boolean}
*/
_scriptDiverged() {
var uiSourceCode = this._breakpoint.uiSourceCode();
if (!uiSourceCode)
return false;
var scriptFile = this._debuggerWorkspaceBinding.scriptFile(uiSourceCode, this.target());
return !!scriptFile && scriptFile.hasDivergedFromVM();
}
/**
* @param {function()} callback
*/
_updateInDebugger(callback) {
if (this.target().isDisposed()) {
this._cleanUpAfterDebuggerIsGone();
callback();
return;
}
var uiSourceCode = this._breakpoint.uiSourceCode();
var lineNumber = this._breakpoint._lineNumber;
var columnNumber = this._breakpoint._columnNumber;
var condition = this._breakpoint.condition();
var debuggerLocation = uiSourceCode ?
this._debuggerWorkspaceBinding.uiLocationToRawLocation(this.target(), uiSourceCode, lineNumber, columnNumber) :
null;
var newState;
if (this._breakpoint._isRemoved || !this._breakpoint.enabled() || this._scriptDiverged()) {
newState = null;
} else if (debuggerLocation) {
var script = debuggerLocation.script();
if (script.sourceURL) {
newState = new Bindings.BreakpointManager.Breakpoint.State(
script.sourceURL, null, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition);
} else {
newState = new Bindings.BreakpointManager.Breakpoint.State(
null, debuggerLocation.scriptId, debuggerLocation.lineNumber, debuggerLocation.columnNumber, condition);
}
} else if (this._breakpoint._currentState && this._breakpoint._currentState.url) {
var position = this._breakpoint._currentState;
newState = new Bindings.BreakpointManager.Breakpoint.State(
position.url, null, position.lineNumber, position.columnNumber, condition);
} else if (uiSourceCode) {
newState = new Bindings.BreakpointManager.Breakpoint.State(
uiSourceCode.url(), null, lineNumber, columnNumber, condition);
}
if (this._debuggerId && Bindings.BreakpointManager.Breakpoint.State.equals(newState, this._currentState)) {
callback();
return;
}
this._breakpoint._currentState = newState;
if (this._debuggerId) {
this._resetLocations();
this._debuggerModel.removeBreakpoint(this._debuggerId, this._didRemoveFromDebugger.bind(this, callback));
this._scheduleUpdateInDebugger();
this._currentState = null;
return;
}
if (!newState) {
callback();
return;
}
var updateCallback = this._didSetBreakpointInDebugger.bind(this, callback);
if (newState.url) {
this._debuggerModel.setBreakpointByURL(
newState.url, newState.lineNumber, newState.columnNumber, this._breakpoint.condition(), updateCallback);
} else if (newState.scriptId) {
this._debuggerModel.setBreakpointBySourceId(
/** @type {!SDK.DebuggerModel.Location} */ (debuggerLocation), condition, updateCallback);
}
this._currentState = newState;
}
/**
* @param {function()} callback
* @param {?Protocol.Debugger.BreakpointId} breakpointId
* @param {!Array.<!SDK.DebuggerModel.Location>} locations
*/
_didSetBreakpointInDebugger(callback, breakpointId, locations) {
if (this._cancelCallback) {
this._cancelCallback = false;
callback();
return;
}
if (!breakpointId) {
this._breakpoint.remove(true);
callback();
return;
}
this._debuggerId = breakpointId;
this._debuggerModel.addBreakpointListener(this._debuggerId, this._breakpointResolved, this);
for (var i = 0; i < locations.length; ++i) {
if (!this._addResolvedLocation(locations[i]))
break;
}
callback();
}
/**
* @param {function()} callback
*/
_didRemoveFromDebugger(callback) {
if (this._cancelCallback) {
this._cancelCallback = false;
callback();
return;
}
this._resetLocations();
this._debuggerModel.removeBreakpointListener(this._debuggerId, this._breakpointResolved, this);
delete this._debuggerId;
callback();
}
/**
* @param {!Common.Event} event
*/
_breakpointResolved(event) {
this._addResolvedLocation(/** @type {!SDK.DebuggerModel.Location}*/ (event.data));
}
/**
* @param {!SDK.DebuggerModel.Location} location
* @param {!Bindings.LiveLocation} liveLocation
*/
_locationUpdated(location, liveLocation) {
var uiLocation = liveLocation.uiLocation();
if (!uiLocation)
return;
var oldUILocation = this._uiLocations.get(location.id()) || null;
this._uiLocations.set(location.id(), uiLocation);
this._breakpoint._replaceUILocation(oldUILocation, uiLocation);
}
/**
* @param {!SDK.DebuggerModel.Location} location
* @return {boolean}
*/
_addResolvedLocation(location) {
var uiLocation = this._debuggerWorkspaceBinding.rawLocationToUILocation(location);
var breakpoint = this._breakpoint._breakpointManager.findBreakpoint(
uiLocation.uiSourceCode, uiLocation.lineNumber, uiLocation.columnNumber);
if (breakpoint && breakpoint !== this._breakpoint) {
// location clash
this._breakpoint.remove();
return false;
}
this._debuggerWorkspaceBinding.createLiveLocation(
location, this._locationUpdated.bind(this, location), this._liveLocations);
return true;
}
_cleanUpAfterDebuggerIsGone() {
if (this._isUpdating)
this._cancelCallback = true;
this._resetLocations();
this._currentState = null;
if (this._debuggerId)
this._didRemoveFromDebugger(function() {});
}
_removeEventListeners() {
this._debuggerModel.removeEventListener(
SDK.DebuggerModel.Events.DebuggerWasDisabled, this._cleanUpAfterDebuggerIsGone, this);
this._debuggerModel.removeEventListener(
SDK.DebuggerModel.Events.DebuggerWasEnabled, this._scheduleUpdateInDebugger, this);
}
};
/**
* @unrestricted
*/
Bindings.BreakpointManager.Breakpoint.State = class {
/**
* @param {?string} url
* @param {?string} scriptId
* @param {number} lineNumber
* @param {number} columnNumber
* @param {string} condition
*/
constructor(url, scriptId, lineNumber, columnNumber, condition) {
this.url = url;
this.scriptId = scriptId;
this.lineNumber = lineNumber;
this.columnNumber = columnNumber;
this.condition = condition;
}
/**
* @param {?Bindings.BreakpointManager.Breakpoint.State|undefined} stateA
* @param {?Bindings.BreakpointManager.Breakpoint.State|undefined} stateB
* @return {boolean}
*/
static equals(stateA, stateB) {
if (!stateA || !stateB)
return false;
if (stateA.scriptId || stateB.scriptId)
return false;
return stateA.url === stateB.url && stateA.lineNumber === stateB.lineNumber &&
stateA.columnNumber === stateB.columnNumber && stateA.condition === stateB.condition;
}
};
/**
* @unrestricted
*/
Bindings.BreakpointManager.Storage = class {
/**
* @param {!Bindings.BreakpointManager} breakpointManager
* @param {?Common.Setting} setting
*/
constructor(breakpointManager, setting) {
this._breakpointManager = breakpointManager;
this._setting = setting || Common.settings.createLocalSetting('breakpoints', []);
var breakpoints = this._setting.get();
/** @type {!Object.<string, !Bindings.BreakpointManager.Storage.Item>} */
this._breakpoints = {};
for (var i = 0; i < breakpoints.length; ++i) {
var breakpoint = /** @type {!Bindings.BreakpointManager.Storage.Item} */ (breakpoints[i]);
breakpoint.columnNumber = breakpoint.columnNumber || 0;
this._breakpoints[breakpoint.sourceFileId + ':' + breakpoint.lineNumber + ':' + breakpoint.columnNumber] =
breakpoint;
}
}
mute() {
this._muted = true;
}
unmute() {
delete this._muted;
}
/**
* @param {string} sourceFileId
* @return {!Array.<!Bindings.BreakpointManager.Storage.Item>}
*/
breakpointItems(sourceFileId) {
var result = [];
for (var id in this._breakpoints) {
var breakpoint = this._breakpoints[id];
if (breakpoint.sourceFileId === sourceFileId)
result.push(breakpoint);
}
return result;
}
/**
* @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
*/
_updateBreakpoint(breakpoint) {
if (this._muted || !breakpoint._breakpointStorageId())
return;
this._breakpoints[breakpoint._breakpointStorageId()] = new Bindings.BreakpointManager.Storage.Item(breakpoint);
this._save();
}
/**
* @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
*/
_removeBreakpoint(breakpoint) {
if (this._muted)
return;
delete this._breakpoints[breakpoint._breakpointStorageId()];
this._save();
}
_save() {
var breakpointsArray = [];
for (var id in this._breakpoints)
breakpointsArray.push(this._breakpoints[id]);
this._setting.set(breakpointsArray);
}
};
/**
* @unrestricted
*/
Bindings.BreakpointManager.Storage.Item = class {
/**
* @param {!Bindings.BreakpointManager.Breakpoint} breakpoint
*/
constructor(breakpoint) {
this.sourceFileId = breakpoint._sourceFileId;
this.lineNumber = breakpoint.lineNumber();
this.columnNumber = breakpoint.columnNumber();
this.condition = breakpoint.condition();
this.enabled = breakpoint.enabled();
}
};
/** @type {!Bindings.BreakpointManager} */
Bindings.breakpointManager;