blob: 8aa49995c3e3297371fe74727466695371884a85 [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 {UI.ContextFlavorListener}
* @unrestricted
*/
Components.DOMBreakpointsSidebarPane = class extends Components.BreakpointsSidebarPaneBase {
constructor() {
super();
this._domBreakpointsSetting = Common.settings.createLocalSetting('domBreakpoints', []);
this.listElement.classList.add('dom-breakpoints-list');
/** @type {!Map<string, !Element>} */
this._breakpointElements = new Map();
SDK.targetManager.addModelListener(SDK.DOMModel, SDK.DOMModel.Events.NodeRemoved, this._nodeRemoved, this);
this._update();
}
/**
* @param {!SDK.DebuggerPausedDetails} details
* @return {!Element}
*/
static createBreakpointHitMessage(details) {
var messageWrapper = createElement('span');
var mainElement = messageWrapper.createChild('div', 'status-main');
mainElement.appendChild(UI.Icon.create('smallicon-info', 'status-icon'));
var auxData = /** @type {!Object} */ (details.auxData);
mainElement.appendChild(createTextNode(
String.sprintf('Paused on %s', Components.DOMBreakpointsSidebarPane.BreakpointTypeNouns[auxData['type']])));
var domModel = SDK.DOMModel.fromTarget(details.target());
if (domModel) {
var subElement = messageWrapper.createChild('div', 'status-sub monospace');
var node = domModel.nodeForId(auxData['nodeId']);
var linkifiedNode = Components.DOMPresentationUtils.linkifyNodeReference(node);
subElement.appendChild(linkifiedNode);
var targetNode = auxData['targetNodeId'] ? domModel.nodeForId(auxData['targetNodeId']) : null;
var targetNodeLink = targetNode ? Components.DOMPresentationUtils.linkifyNodeReference(targetNode) : '';
var message;
if (auxData.type === Components.DOMBreakpointsSidebarPane.BreakpointTypes.SubtreeModified) {
if (auxData['insertion'])
message = targetNode === node ? 'Child %s added' : 'Descendant %s added';
else
message = 'Descendant %s removed';
subElement.appendChild(createElement('br'));
subElement.appendChild(UI.formatLocalized(message, [targetNodeLink]));
}
}
return messageWrapper;
}
/**
* @param {!SDK.DOMNode} node
* @param {!UI.ContextMenu} contextMenu
* @param {boolean} createSubMenu
*/
populateNodeContextMenu(node, contextMenu, createSubMenu) {
if (node.pseudoType())
return;
var nodeBreakpoints = this._nodeBreakpoints(node);
/**
* @param {!Protocol.DOMDebugger.DOMBreakpointType} type
* @this {Components.DOMBreakpointsSidebarPane}
*/
function toggleBreakpoint(type) {
if (!nodeBreakpoints.has(type))
this._setBreakpoint(node, type, true);
else
this._removeBreakpoint(node, type);
this._saveBreakpoints();
}
var breakpointsMenu = createSubMenu ? contextMenu.appendSubMenuItem(Common.UIString('Break on...')) : contextMenu;
for (var key in Components.DOMBreakpointsSidebarPane.BreakpointTypes) {
var type = Components.DOMBreakpointsSidebarPane.BreakpointTypes[key];
var label = Components.DOMBreakpointsSidebarPane.BreakpointTypeNouns[type];
breakpointsMenu.appendCheckboxItem(label, toggleBreakpoint.bind(this, type), nodeBreakpoints.has(type));
}
}
/**
* @param {!SDK.DOMNode} node
* @return {!Set<!Protocol.DOMDebugger.DOMBreakpointType>}
*/
_nodeBreakpoints(node) {
/** @type {!Set<!Protocol.DOMDebugger.DOMBreakpointType>} */
var nodeBreakpoints = new Set();
for (var element of this._breakpointElements.values()) {
if (element._node === node && element._checkboxElement.checked)
nodeBreakpoints.add(element._type);
}
return nodeBreakpoints;
}
/**
* @param {!SDK.DOMNode} node
* @return {boolean}
*/
hasBreakpoints(node) {
for (var element of this._breakpointElements.values()) {
if (element._node === node && element._checkboxElement.checked)
return true;
}
return false;
}
_nodeRemoved(event) {
var node = event.data.node;
this._removeBreakpointsForNode(event.data.node);
var children = node.children();
if (!children)
return;
for (var i = 0; i < children.length; ++i)
this._removeBreakpointsForNode(children[i]);
this._saveBreakpoints();
}
/**
* @param {!SDK.DOMNode} node
*/
_removeBreakpointsForNode(node) {
for (var element of this._breakpointElements.values()) {
if (element._node === node)
this._removeBreakpoint(element._node, element._type);
}
}
/**
* @param {!SDK.DOMNode} node
* @param {!Protocol.DOMDebugger.DOMBreakpointType} type
* @param {boolean} enabled
*/
_setBreakpoint(node, type, enabled) {
var breakpointId = this._createBreakpointId(node.id, type);
var breakpointElement = this._breakpointElements.get(breakpointId);
if (!breakpointElement) {
breakpointElement = this._createBreakpointElement(node, type, enabled);
this._breakpointElements.set(breakpointId, breakpointElement);
} else {
breakpointElement._checkboxElement.checked = enabled;
}
if (enabled)
node.target().domdebuggerAgent().setDOMBreakpoint(node.id, type);
node.setMarker(Components.DOMBreakpointsSidebarPane.Marker, true);
}
/**
* @param {!SDK.DOMNode} node
* @param {!Protocol.DOMDebugger.DOMBreakpointType} type
* @param {boolean} enabled
*/
_createBreakpointElement(node, type, enabled) {
var element = createElement('li');
element._node = node;
element._type = type;
element.addEventListener('contextmenu', this._contextMenu.bind(this, node, type), true);
var checkboxLabel = createCheckboxLabel('', enabled);
var checkboxElement = checkboxLabel.checkboxElement;
checkboxElement.addEventListener('click', this._checkboxClicked.bind(this, node, type), false);
element._checkboxElement = checkboxElement;
element.appendChild(checkboxLabel);
var labelElement = createElementWithClass('div', 'dom-breakpoint');
element.appendChild(labelElement);
var linkifiedNode = Components.DOMPresentationUtils.linkifyNodeReference(node);
linkifiedNode.classList.add('monospace');
linkifiedNode.style.display = 'block';
labelElement.appendChild(linkifiedNode);
var description = createElement('div');
description.textContent = Components.DOMBreakpointsSidebarPane.BreakpointTypeLabels[type];
labelElement.appendChild(description);
var currentElement = this.listElement.firstChild;
while (currentElement) {
if (currentElement._type && currentElement._type < element._type)
break;
currentElement = currentElement.nextSibling;
}
this.addListElement(element, currentElement);
return element;
}
_removeAllBreakpoints() {
for (var element of this._breakpointElements.values())
this._removeBreakpoint(element._node, element._type);
this._saveBreakpoints();
}
/**
* @param {!SDK.DOMNode} node
* @param {!Protocol.DOMDebugger.DOMBreakpointType} type
*/
_removeBreakpoint(node, type) {
var breakpointId = this._createBreakpointId(node.id, type);
var element = this._breakpointElements.get(breakpointId);
if (!element)
return;
this.removeListElement(element);
this._breakpointElements.delete(breakpointId);
if (element._checkboxElement.checked)
node.target().domdebuggerAgent().removeDOMBreakpoint(node.id, type);
node.setMarker(Components.DOMBreakpointsSidebarPane.Marker, this.hasBreakpoints(node) ? true : null);
}
/**
* @param {!SDK.DOMNode} node
* @param {!Protocol.DOMDebugger.DOMBreakpointType} type
* @param {!Event} event
*/
_contextMenu(node, type, event) {
var contextMenu = new UI.ContextMenu(event);
/**
* @this {Components.DOMBreakpointsSidebarPane}
*/
function removeBreakpoint() {
this._removeBreakpoint(node, type);
this._saveBreakpoints();
}
contextMenu.appendItem(Common.UIString.capitalize('Remove ^breakpoint'), removeBreakpoint.bind(this));
contextMenu.appendItem(
Common.UIString.capitalize('Remove ^all DOM breakpoints'), this._removeAllBreakpoints.bind(this));
contextMenu.show();
}
/**
* @param {!SDK.DOMNode} node
* @param {!Protocol.DOMDebugger.DOMBreakpointType} type
* @param {!Event} event
*/
_checkboxClicked(node, type, event) {
if (event.target.checked)
node.target().domdebuggerAgent().setDOMBreakpoint(node.id, type);
else
node.target().domdebuggerAgent().removeDOMBreakpoint(node.id, type);
this._saveBreakpoints();
}
/**
* @override
* @param {?Object} object
*/
flavorChanged(object) {
this._update();
}
_update() {
var details = UI.context.flavor(SDK.DebuggerPausedDetails);
if (!details || details.reason !== SDK.DebuggerModel.BreakReason.DOM) {
if (this._highlightedElement) {
this._highlightedElement.classList.remove('breakpoint-hit');
delete this._highlightedElement;
}
return;
}
var auxData = details.auxData;
var breakpointId = this._createBreakpointId(auxData.nodeId, auxData.type);
var element = this._breakpointElements.get(breakpointId);
if (!element)
return;
UI.viewManager.showView('sources.domBreakpoints');
element.classList.add('breakpoint-hit');
this._highlightedElement = element;
}
/**
* @param {number} nodeId
* @param {!Protocol.DOMDebugger.DOMBreakpointType} type
*/
_createBreakpointId(nodeId, type) {
return nodeId + ':' + type;
}
_saveBreakpoints() {
var breakpoints = [];
var storedBreakpoints = this._domBreakpointsSetting.get();
for (var i = 0; i < storedBreakpoints.length; ++i) {
var breakpoint = storedBreakpoints[i];
if (breakpoint.url !== this._inspectedURL)
breakpoints.push(breakpoint);
}
for (var element of this._breakpointElements.values()) {
breakpoints.push({
url: this._inspectedURL,
path: element._node.path(),
type: element._type,
enabled: element._checkboxElement.checked
});
}
this._domBreakpointsSetting.set(breakpoints);
}
/**
* @param {!SDK.DOMDocument} domDocument
*/
restoreBreakpoints(domDocument) {
this._breakpointElements.clear();
this.reset();
this._inspectedURL = domDocument.documentURL;
var domModel = domDocument.domModel();
/** @type {!Map<string, !Array<!Object>>} */
var pathToBreakpoints = new Map();
/**
* @param {string} path
* @param {?Protocol.DOM.NodeId} nodeId
* @this {Components.DOMBreakpointsSidebarPane}
*/
function didPushNodeByPathToFrontend(path, nodeId) {
var node = nodeId ? domModel.nodeForId(nodeId) : null;
if (!node)
return;
var breakpoints = pathToBreakpoints.get(path);
for (var i = 0; i < breakpoints.length; ++i)
this._setBreakpoint(node, breakpoints[i].type, breakpoints[i].enabled);
}
var breakpoints = this._domBreakpointsSetting.get();
for (var i = 0; i < breakpoints.length; ++i) {
var breakpoint = breakpoints[i];
if (breakpoint.url !== this._inspectedURL)
continue;
var path = breakpoint.path;
if (!pathToBreakpoints.has(path)) {
pathToBreakpoints.set(path, []);
domModel.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path));
}
pathToBreakpoints.get(path).push(breakpoint);
}
}
};
Components.DOMBreakpointsSidebarPane.BreakpointTypes = {
SubtreeModified: 'subtree-modified',
AttributeModified: 'attribute-modified',
NodeRemoved: 'node-removed'
};
Components.DOMBreakpointsSidebarPane.BreakpointTypeLabels = {
'subtree-modified': Common.UIString('Subtree Modified'),
'attribute-modified': Common.UIString('Attribute Modified'),
'node-removed': Common.UIString('Node Removed')
};
Components.DOMBreakpointsSidebarPane.BreakpointTypeNouns = {
'subtree-modified': Common.UIString('subtree modifications'),
'attribute-modified': Common.UIString('attribute modifications'),
'node-removed': Common.UIString('node removal')
};
Components.DOMBreakpointsSidebarPane.Marker = 'breakpoint-marker';
/**
* @unrestricted
*/
Components.DOMBreakpointsSidebarPane.Proxy = class extends UI.VBox {
constructor() {
super();
this.registerRequiredCSS('components/breakpointsList.css');
}
/**
* @override
*/
wasShown() {
super.wasShown();
var pane = Components.domBreakpointsSidebarPane;
if (pane.element.parentNode !== this.element)
pane.show(this.element);
}
};
/**
* @type {!Components.DOMBreakpointsSidebarPane}
*/
Components.domBreakpointsSidebarPane;