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