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