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 /** 32 * @constructor 33 * @extends {WebInspector.NativeBreakpointsSidebarPane} 34 */ 35 WebInspector.DOMBreakpointsSidebarPane = function() 36 { 37 WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("DOM Breakpoints")); 38 39 this._breakpointElements = {}; 40 41 this._breakpointTypes = { 42 SubtreeModified: "subtree-modified", 43 AttributeModified: "attribute-modified", 44 NodeRemoved: "node-removed" 45 }; 46 this._breakpointTypeLabels = {}; 47 this._breakpointTypeLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString("Subtree Modified"); 48 this._breakpointTypeLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString("Attribute Modified"); 49 this._breakpointTypeLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString("Node Removed"); 50 51 this._contextMenuLabels = {}; 52 this._contextMenuLabels[this._breakpointTypes.SubtreeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Subtree modifications" : "Subtree Modifications"); 53 this._contextMenuLabels[this._breakpointTypes.AttributeModified] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Attributes modifications" : "Attributes Modifications"); 54 this._contextMenuLabels[this._breakpointTypes.NodeRemoved] = WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Node removal" : "Node Removal"); 55 56 WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged, this._inspectedURLChanged, this); 57 WebInspector.domAgent.addEventListener(WebInspector.DOMAgent.Events.NodeRemoved, this._nodeRemoved, this); 58 } 59 60 WebInspector.DOMBreakpointsSidebarPane.prototype = { 61 _inspectedURLChanged: function(event) 62 { 63 this._breakpointElements = {}; 64 this._reset(); 65 var url = event.data; 66 this._inspectedURL = url.removeURLFragment(); 67 }, 68 69 populateNodeContextMenu: function(node, contextMenu) 70 { 71 var nodeBreakpoints = {}; 72 for (var id in this._breakpointElements) { 73 var element = this._breakpointElements[id]; 74 if (element._node === node) 75 nodeBreakpoints[element._type] = true; 76 } 77 78 function toggleBreakpoint(type) 79 { 80 if (!nodeBreakpoints[type]) 81 this._setBreakpoint(node, type, true); 82 else 83 this._removeBreakpoint(node, type); 84 this._saveBreakpoints(); 85 } 86 87 var breakPointSubMenu = contextMenu.appendSubMenuItem(WebInspector.UIString("Break on...")); 88 for (var key in this._breakpointTypes) { 89 var type = this._breakpointTypes[key]; 90 var label = this._contextMenuLabels[type]; 91 breakPointSubMenu.appendCheckboxItem(label, toggleBreakpoint.bind(this, type), nodeBreakpoints[type]); 92 } 93 }, 94 95 createBreakpointHitStatusMessage: function(auxData, callback) 96 { 97 if (auxData.type === this._breakpointTypes.SubtreeModified) { 98 var targetNodeObject = WebInspector.RemoteObject.fromPayload(auxData["targetNode"]); 99 function didPushNodeToFrontend(targetNodeId) 100 { 101 if (targetNodeId) 102 targetNodeObject.release(); 103 this._doCreateBreakpointHitStatusMessage(auxData, targetNodeId, callback); 104 } 105 targetNodeObject.pushNodeToFrontend(didPushNodeToFrontend.bind(this)); 106 } else 107 this._doCreateBreakpointHitStatusMessage(auxData, null, callback); 108 }, 109 110 _doCreateBreakpointHitStatusMessage: function (auxData, targetNodeId, callback) 111 { 112 var message; 113 var typeLabel = this._breakpointTypeLabels[auxData.type]; 114 var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeById(auxData.nodeId); 115 var substitutions = [typeLabel, linkifiedNode]; 116 var targetNode = ""; 117 if (targetNodeId) 118 targetNode = WebInspector.DOMPresentationUtils.linkifyNodeById(targetNodeId); 119 120 if (auxData.type === this._breakpointTypes.SubtreeModified) { 121 if (auxData.insertion) { 122 if (targetNodeId !== auxData.nodeId) { 123 message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to its descendant %s."; 124 substitutions.push(targetNode); 125 } else 126 message = "Paused on a \"%s\" breakpoint set on %s, because a new child was added to that node."; 127 } else { 128 message = "Paused on a \"%s\" breakpoint set on %s, because its descendant %s was removed."; 129 substitutions.push(targetNode); 130 } 131 } else 132 message = "Paused on a \"%s\" breakpoint set on %s."; 133 134 var element = document.createElement("span"); 135 var formatters = { 136 s: function(substitution) 137 { 138 return substitution; 139 } 140 }; 141 function append(a, b) 142 { 143 if (typeof b === "string") 144 b = document.createTextNode(b); 145 element.appendChild(b); 146 } 147 WebInspector.formatLocalized(message, substitutions, formatters, "", append); 148 149 callback(element); 150 }, 151 152 _nodeRemoved: function(event) 153 { 154 var node = event.data.node; 155 this._removeBreakpointsForNode(event.data.node); 156 var children = node.children(); 157 if (!children) 158 return; 159 for (var i = 0; i < children.length; ++i) 160 this._removeBreakpointsForNode(children[i]); 161 this._saveBreakpoints(); 162 }, 163 164 _removeBreakpointsForNode: function(node) 165 { 166 for (var id in this._breakpointElements) { 167 var element = this._breakpointElements[id]; 168 if (element._node === node) 169 this._removeBreakpoint(element._node, element._type); 170 } 171 }, 172 173 _setBreakpoint: function(node, type, enabled) 174 { 175 var breakpointId = this._createBreakpointId(node.id, type); 176 if (breakpointId in this._breakpointElements) 177 return; 178 179 var element = document.createElement("li"); 180 element._node = node; 181 element._type = type; 182 element.addEventListener("contextmenu", this._contextMenu.bind(this, node, type), true); 183 184 var checkboxElement = document.createElement("input"); 185 checkboxElement.className = "checkbox-elem"; 186 checkboxElement.type = "checkbox"; 187 checkboxElement.checked = enabled; 188 checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, node, type), false); 189 element._checkboxElement = checkboxElement; 190 element.appendChild(checkboxElement); 191 192 var labelElement = document.createElement("span"); 193 element.appendChild(labelElement); 194 195 var linkifiedNode = WebInspector.DOMPresentationUtils.linkifyNodeById(node.id); 196 linkifiedNode.addStyleClass("monospace"); 197 labelElement.appendChild(linkifiedNode); 198 199 var description = document.createElement("div"); 200 description.className = "source-text"; 201 description.textContent = this._breakpointTypeLabels[type]; 202 labelElement.appendChild(description); 203 204 var currentElement = this.listElement.firstChild; 205 while (currentElement) { 206 if (currentElement._type && currentElement._type < element._type) 207 break; 208 currentElement = currentElement.nextSibling; 209 } 210 this._addListElement(element, currentElement); 211 this._breakpointElements[breakpointId] = element; 212 if (enabled) 213 DOMDebuggerAgent.setDOMBreakpoint(node.id, type); 214 }, 215 216 _removeAllBreakpoints: function() 217 { 218 for (var id in this._breakpointElements) { 219 var element = this._breakpointElements[id]; 220 this._removeBreakpoint(element._node, element._type); 221 } 222 this._saveBreakpoints(); 223 }, 224 225 _removeBreakpoint: function(node, type) 226 { 227 var breakpointId = this._createBreakpointId(node.id, type); 228 var element = this._breakpointElements[breakpointId]; 229 if (!element) 230 return; 231 232 this._removeListElement(element); 233 delete this._breakpointElements[breakpointId]; 234 if (element._checkboxElement.checked) 235 DOMDebuggerAgent.removeDOMBreakpoint(node.id, type); 236 }, 237 238 _contextMenu: function(node, type, event) 239 { 240 var contextMenu = new WebInspector.ContextMenu(event); 241 function removeBreakpoint() 242 { 243 this._removeBreakpoint(node, type); 244 this._saveBreakpoints(); 245 } 246 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove breakpoint" : "Remove Breakpoint"), removeBreakpoint.bind(this)); 247 contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Remove all DOM breakpoints" : "Remove All DOM Breakpoints"), this._removeAllBreakpoints.bind(this)); 248 contextMenu.show(); 249 }, 250 251 _checkboxClicked: function(node, type, event) 252 { 253 if (event.target.checked) 254 DOMDebuggerAgent.setDOMBreakpoint(node.id, type); 255 else 256 DOMDebuggerAgent.removeDOMBreakpoint(node.id, type); 257 this._saveBreakpoints(); 258 }, 259 260 highlightBreakpoint: function(auxData) 261 { 262 var breakpointId = this._createBreakpointId(auxData.nodeId, auxData.type); 263 var element = this._breakpointElements[breakpointId]; 264 if (!element) 265 return; 266 this.expand(); 267 element.addStyleClass("breakpoint-hit"); 268 this._highlightedElement = element; 269 }, 270 271 clearBreakpointHighlight: function() 272 { 273 if (this._highlightedElement) { 274 this._highlightedElement.removeStyleClass("breakpoint-hit"); 275 delete this._highlightedElement; 276 } 277 }, 278 279 _createBreakpointId: function(nodeId, type) 280 { 281 return nodeId + ":" + type; 282 }, 283 284 _saveBreakpoints: function() 285 { 286 var breakpoints = []; 287 var storedBreakpoints = WebInspector.settings.domBreakpoints.get(); 288 for (var i = 0; i < storedBreakpoints.length; ++i) { 289 var breakpoint = storedBreakpoints[i]; 290 if (breakpoint.url !== this._inspectedURL) 291 breakpoints.push(breakpoint); 292 } 293 for (var id in this._breakpointElements) { 294 var element = this._breakpointElements[id]; 295 breakpoints.push({ url: this._inspectedURL, path: element._node.path(), type: element._type, enabled: element._checkboxElement.checked }); 296 } 297 WebInspector.settings.domBreakpoints.set(breakpoints); 298 }, 299 300 restoreBreakpoints: function() 301 { 302 var pathToBreakpoints = {}; 303 304 /** 305 * @param {string} path 306 * @param {?DOMAgent.NodeId} nodeId 307 */ 308 function didPushNodeByPathToFrontend(path, nodeId) 309 { 310 var node = nodeId ? WebInspector.domAgent.nodeForId(nodeId) : null; 311 if (!node) 312 return; 313 314 var breakpoints = pathToBreakpoints[path]; 315 for (var i = 0; i < breakpoints.length; ++i) 316 this._setBreakpoint(node, breakpoints[i].type, breakpoints[i].enabled); 317 } 318 319 var breakpoints = WebInspector.settings.domBreakpoints.get(); 320 for (var i = 0; i < breakpoints.length; ++i) { 321 var breakpoint = breakpoints[i]; 322 if (breakpoint.url !== this._inspectedURL) 323 continue; 324 var path = breakpoint.path; 325 if (!pathToBreakpoints[path]) { 326 pathToBreakpoints[path] = []; 327 WebInspector.domAgent.pushNodeByPathToFrontend(path, didPushNodeByPathToFrontend.bind(this, path)); 328 } 329 pathToBreakpoints[path].push(breakpoint); 330 } 331 }, 332 333 /** 334 * @param {WebInspector.Panel} panel 335 */ 336 createProxy: function(panel) 337 { 338 var proxy = new WebInspector.DOMBreakpointsSidebarPane.Proxy(this, panel); 339 if (!this._proxies) 340 this._proxies = []; 341 this._proxies.push(proxy); 342 return proxy; 343 }, 344 345 onContentReady: function() 346 { 347 for (var i = 0; i != this._proxies.length; i++) 348 this._proxies[i].onContentReady(); 349 }, 350 351 __proto__: WebInspector.NativeBreakpointsSidebarPane.prototype 352 } 353 354 /** 355 * @constructor 356 * @extends {WebInspector.SidebarPane} 357 * @param {WebInspector.DOMBreakpointsSidebarPane} pane 358 * @param {WebInspector.Panel} panel 359 */ 360 WebInspector.DOMBreakpointsSidebarPane.Proxy = function(pane, panel) 361 { 362 WebInspector.View._assert(!pane.titleElement.firstChild, "Cannot create proxy for a sidebar pane with a toolbar"); 363 364 WebInspector.SidebarPane.call(this, pane.title()); 365 this.registerRequiredCSS("breakpointsList.css"); 366 367 this._wrappedPane = pane; 368 this._panel = panel; 369 370 this.bodyElement.remove(); 371 this.bodyElement = this._wrappedPane.bodyElement; 372 } 373 374 WebInspector.DOMBreakpointsSidebarPane.Proxy.prototype = { 375 expand: function() 376 { 377 this._wrappedPane.expand(); 378 }, 379 380 onContentReady: function() 381 { 382 if (this._panel.isShowing()) 383 this._reattachBody(); 384 385 WebInspector.SidebarPane.prototype.onContentReady.call(this); 386 }, 387 388 wasShown: function() 389 { 390 WebInspector.SidebarPane.prototype.wasShown.call(this); 391 this._reattachBody(); 392 }, 393 394 _reattachBody: function() 395 { 396 if (this.bodyElement.parentNode !== this.element) 397 this.element.appendChild(this.bodyElement); 398 }, 399 400 __proto__: WebInspector.SidebarPane.prototype 401 } 402 403 /** 404 * @type {?WebInspector.DOMBreakpointsSidebarPane} 405 */ 406 WebInspector.domBreakpointsSidebarPane = null; 407