1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 WebInspector.DOMBreakpointsSidebarPane = function() 32 { 33 WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("DOM Breakpoints")); 34 35 this._breakpointElements = {}; 36 37 this._breakpointTypes = { 38 SubtreeModified: 0, 39 AttributeModified: 1, 40 NodeRemoved: 2 41 }; 42 this._breakpointTypeLabels = [ 43 WebInspector.UIString("Subtree Modified"), 44 WebInspector.UIString("Attribute Modified"), 45 WebInspector.UIString("Node Removed") 46 ]; 47 this._contextMenuLabels = [ 48 WebInspector.UIString("Break on Subtree Modifications"), 49 WebInspector.UIString("Break on Attributes Modifications"), 50 WebInspector.UIString("Break on Node Removal") 51 ]; 52 } 53 54 WebInspector.DOMBreakpointsSidebarPane.prototype = { 55 setInspectedURL: function(url) 56 { 57 this._reset(); 58 this._inspectedURL = url.removeURLFragment(); 59 }, 60 61 populateNodeContextMenu: function(node, contextMenu) 62 { 63 var nodeBreakpoints = {}; 64 for (var id in this._breakpointElements) { 65 var element = this._breakpointElements[id]; 66 if (element._node === node) 67 nodeBreakpoints[element._type] = true; 68 } 69 70 function toggleBreakpoint(type) 71 { 72 if (!nodeBreakpoints[type]) 73 this._setBreakpoint(node, type, true); 74 else 75 this._removeBreakpoint(node, type); 76 this._saveBreakpoints(); 77 } 78 79 for (var type = 0; type < 3; ++type) { 80 var label = this._contextMenuLabels[type]; 81 contextMenu.appendCheckboxItem(label, toggleBreakpoint.bind(this, type), nodeBreakpoints[type]); 82 } 83 }, 84 85 createBreakpointHitStatusMessage: function(eventData, callback) 86 { 87 if (eventData.type === this._breakpointTypes.SubtreeModified) { 88 var targetNodeObject = WebInspector.RemoteObject.fromPayload(eventData.targetNode); 89 function didPushNodeToFrontend(targetNodeId) 90 { 91 if (targetNodeId) 92 targetNodeObject.release(); 93 this._doCreateBreakpointHitStatusMessage(eventData, targetNodeId, callback); 94 } 95 targetNodeObject.pushNodeToFrontend(didPushNodeToFrontend.bind(this)); 96 } else 97 this._doCreateBreakpointHitStatusMessage(eventData, null, callback); 98 }, 99 100 _doCreateBreakpointHitStatusMessage: function (eventData, targetNodeId, callback) 101 { 102 var message; 103 var typeLabel = this._breakpointTypeLabels[eventData.type]; 104 var linkifiedNode = WebInspector.panels.elements.linkifyNodeById(eventData.nodeId); 105 var substitutions = [typeLabel, linkifiedNode]; 106 var targetNode = ""; 107 if (targetNodeId) 108 targetNode = WebInspector.panels.elements.linkifyNodeById(targetNodeId); 109 110 if (eventData.type === this._breakpointTypes.SubtreeModified) { 111 if (eventData.insertion) { 112 if (targetNodeId !== eventData.nodeId) { 113 message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s."; 114 substitutions.push(targetNode); 115 } else 116 message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node."; 117 } else { 118 message = "Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed."; 119 substitutions.push(targetNode); 120 } 121 } else 122 message = "Paused on a \"%s\" breakpoint set on %s."; 123 124 var element = document.createElement("span"); 125 var formatters = { 126 s: function(substitution) 127 { 128 return substitution; 129 } 130 }; 131 function append(a, b) 132 { 133 if (typeof b === "string") 134 b = document.createTextNode(b); 135 element.appendChild(b); 136 } 137 WebInspector.formatLocalized(message, substitutions, formatters, "", append); 138 139 callback(element); 140 }, 141 142 nodeRemoved: function(node) 143 { 144 this._removeBreakpointsForNode(node); 145 if (!node.children) 146 return; 147 for (var i = 0; i < node.children.length; ++i) 148 this._removeBreakpointsForNode(node.children[i]); 149 this._saveBreakpoints(); 150 }, 151 152 _removeBreakpointsForNode: function(node) 153 { 154 for (var id in this._breakpointElements) { 155 var element = this._breakpointElements[id]; 156 if (element._node === node) 157 this._removeBreakpoint(element._node, element._type); 158 } 159 }, 160 161 _setBreakpoint: function(node, type, enabled) 162 { 163 var breakpointId = this._createBreakpointId(node.id, type); 164 if (breakpointId in this._breakpointElements) 165 return; 166 167 var element = document.createElement("li"); 168 element._node = node; 169 element._type = type; 170 element.addEventListener("contextmenu", this._contextMenu.bind(this, node, type), true); 171 172 var checkboxElement = document.createElement("input"); 173 checkboxElement.className = "checkbox-elem"; 174 checkboxElement.type = "checkbox"; 175 checkboxElement.checked = enabled; 176 checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, node, type), false); 177 element._checkboxElement = checkboxElement; 178 element.appendChild(checkboxElement); 179 180 var labelElement = document.createElement("span"); 181 element.appendChild(labelElement); 182 183 var linkifiedNode = WebInspector.panels.elements.linkifyNodeById(node.id); 184 linkifiedNode.addStyleClass("monospace"); 185 labelElement.appendChild(linkifiedNode); 186 187 var description = document.createElement("div"); 188 description.className = "source-text"; 189 description.textContent = this._breakpointTypeLabels[type]; 190 labelElement.appendChild(description); 191 192 var currentElement = this.listElement.firstChild; 193 while (currentElement) { 194 if (currentElement._type && currentElement._type < element._type) 195 break; 196 currentElement = currentElement.nextSibling; 197 } 198 this._addListElement(element, currentElement); 199 this._breakpointElements[breakpointId] = element; 200 if (enabled) 201 BrowserDebuggerAgent.setDOMBreakpoint(node.id, type); 202 }, 203 204 _removeBreakpoint: function(node, type) 205 { 206 var breakpointId = this._createBreakpointId(node.id, type); 207 var element = this._breakpointElements[breakpointId]; 208 if (!element) 209 return; 210 211 this._removeListElement(element); 212 delete this._breakpointElements[breakpointId]; 213 if (element._checkboxElement.checked) 214 BrowserDebuggerAgent.removeDOMBreakpoint(node.id, type); 215 }, 216 217 _contextMenu: function(node, type, event) 218 { 219 var contextMenu = new WebInspector.ContextMenu(); 220 function removeBreakpoint() 221 { 222 this._removeBreakpoint(node, type); 223 this._saveBreakpoints(); 224 } 225 contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeBreakpoint.bind(this)); 226 contextMenu.show(event); 227 }, 228 229 _checkboxClicked: function(node, type, event) 230 { 231 if (event.target.checked) 232 BrowserDebuggerAgent.setDOMBreakpoint(node.id, type); 233 else 234 BrowserDebuggerAgent.removeDOMBreakpoint(node.id, type); 235 this._saveBreakpoints(); 236 }, 237 238 highlightBreakpoint: function(eventData) 239 { 240 var breakpointId = this._createBreakpointId(eventData.nodeId, eventData.type); 241 var element = this._breakpointElements[breakpointId]; 242 if (!element) 243 return; 244 this.expanded = true; 245 element.addStyleClass("breakpoint-hit"); 246 this._highlightedElement = element; 247 }, 248 249 clearBreakpointHighlight: function() 250 { 251 if (this._highlightedElement) { 252 this._highlightedElement.removeStyleClass("breakpoint-hit"); 253 delete this._highlightedElement; 254 } 255 }, 256 257 _createBreakpointId: function(nodeId, type) 258 { 259 return nodeId + ":" + type; 260 }, 261 262 _saveBreakpoints: function() 263 { 264 var breakpoints = []; 265 var storedBreakpoints = WebInspector.settings.domBreakpoints; 266 for (var i = 0; i < storedBreakpoints.length; ++i) { 267 var breakpoint = storedBreakpoints[i]; 268 if (breakpoint.url !== this._inspectedURL) 269 breakpoints.push(breakpoint); 270 } 271 for (var id in this._breakpointElements) { 272 var element = this._breakpointElements[id]; 273 breakpoints.push({ url: this._inspectedURL, path: element._node.path(), type: element._type, enabled: element._checkboxElement.checked }); 274 } 275 WebInspector.settings.domBreakpoints = breakpoints; 276 }, 277 278 restoreBreakpoints: function() 279 { 280 var pathToBreakpoints = {}; 281 282 function didPushNodeByPathToFrontend(path, nodeId) 283 { 284 var node = WebInspector.domAgent.nodeForId(nodeId); 285 if (!node) 286 return; 287 288 var breakpoints = pathToBreakpoints[path]; 289 for (var i = 0; i < breakpoints.length; ++i) 290 this._setBreakpoint(node, breakpoints[i].type, breakpoints[i].enabled); 291 } 292 293 var breakpoints = WebInspector.settings.domBreakpoints; 294 for (var i = 0; i < breakpoints.length; ++i) { 295 var breakpoint = breakpoints[i]; 296 if (breakpoint.url !== this._inspectedURL) 297 continue; 298 var path = breakpoint.path; 299 if (!pathToBreakpoints[path]) { 300 pathToBreakpoints[path] = []; 301 WebInspector.domAgent.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path)); 302 } 303 pathToBreakpoints[path].push(breakpoint); 304 } 305 } 306 } 307 308 WebInspector.DOMBreakpointsSidebarPane.prototype.__proto__ = WebInspector.NativeBreakpointsSidebarPane.prototype; 309