1 /* 2 * Copyright (C) 2008 Apple 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 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 WebInspector.JavaScriptBreakpointsSidebarPane = function(model, showSourceLineDelegate) 27 { 28 WebInspector.SidebarPane.call(this, WebInspector.UIString("Breakpoints")); 29 30 this._model = model; 31 this._showSourceLineDelegate = showSourceLineDelegate; 32 33 this.listElement = document.createElement("ol"); 34 this.listElement.className = "breakpoint-list"; 35 36 this.emptyElement = document.createElement("div"); 37 this.emptyElement.className = "info"; 38 this.emptyElement.textContent = WebInspector.UIString("No Breakpoints"); 39 40 this.bodyElement.appendChild(this.emptyElement); 41 42 this._items = {}; 43 } 44 45 WebInspector.JavaScriptBreakpointsSidebarPane.prototype = { 46 addBreakpoint: function(breakpoint) 47 { 48 var element = document.createElement("li"); 49 element.addStyleClass("cursor-pointer"); 50 element.addEventListener("contextmenu", this._contextMenu.bind(this, breakpoint), true); 51 element.addEventListener("click", this._breakpointClicked.bind(this, breakpoint), false); 52 53 var checkbox = document.createElement("input"); 54 checkbox.className = "checkbox-elem"; 55 checkbox.type = "checkbox"; 56 checkbox.checked = breakpoint.enabled; 57 checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, breakpoint), false); 58 element.appendChild(checkbox); 59 60 var displayName = breakpoint.url ? WebInspector.displayNameForURL(breakpoint.url) : WebInspector.UIString("(program)"); 61 var labelElement = document.createTextNode(displayName + ":" + (breakpoint.lineNumber + 1)); 62 element.appendChild(labelElement); 63 64 var snippetElement = document.createElement("div"); 65 snippetElement.className = "source-text monospace"; 66 element.appendChild(snippetElement); 67 if (breakpoint.loadSnippet) { 68 function didLoadSnippet(snippet) 69 { 70 snippetElement.textContent = snippet; 71 } 72 breakpoint.loadSnippet(didLoadSnippet); 73 } 74 75 element._data = breakpoint; 76 var currentElement = this.listElement.firstChild; 77 while (currentElement) { 78 if (currentElement._data && this._compareBreakpoints(currentElement._data, element._data) > 0) 79 break; 80 currentElement = currentElement.nextSibling; 81 } 82 this._addListElement(element, currentElement); 83 84 var breakpointItem = {}; 85 breakpointItem.element = element; 86 breakpointItem.checkbox = checkbox; 87 this._items[this._createBreakpointItemId(breakpoint.sourceFileId, breakpoint.lineNumber)] = breakpointItem; 88 89 if (!this.expanded) 90 this.expanded = true; 91 }, 92 93 removeBreakpoint: function(sourceFileId, lineNumber) 94 { 95 var breakpointItemId = this._createBreakpointItemId(sourceFileId, lineNumber); 96 var breakpointItem = this._items[breakpointItemId]; 97 if (!breakpointItem) 98 return; 99 delete this._items[breakpointItemId]; 100 this._removeListElement(breakpointItem.element); 101 }, 102 103 highlightBreakpoint: function(sourceFileId, lineNumber) 104 { 105 var breakpointItem = this._items[this._createBreakpointItemId(sourceFileId, lineNumber)]; 106 if (!breakpointItem) 107 return; 108 breakpointItem.element.addStyleClass("breakpoint-hit"); 109 this._highlightedBreakpointItem = breakpointItem; 110 }, 111 112 clearBreakpointHighlight: function() 113 { 114 if (this._highlightedBreakpointItem) { 115 this._highlightedBreakpointItem.element.removeStyleClass("breakpoint-hit"); 116 delete this._highlightedBreakpointItem; 117 } 118 }, 119 120 _createBreakpointItemId: function(sourceFileId, lineNumber) 121 { 122 return sourceFileId + ":" + lineNumber; 123 }, 124 125 _breakpointClicked: function(breakpoint, event) 126 { 127 this._showSourceLineDelegate(breakpoint.sourceFileId, breakpoint.lineNumber); 128 }, 129 130 _breakpointCheckboxClicked: function(breakpoint, event) 131 { 132 // Breakpoint element has it's own click handler. 133 event.stopPropagation(); 134 135 this._model.setBreakpointEnabled(breakpoint.sourceFileId, breakpoint.lineNumber, event.target.checked); 136 }, 137 138 _contextMenu: function(breakpoint, event) 139 { 140 var contextMenu = new WebInspector.ContextMenu(); 141 142 var removeHandler = this._model.removeBreakpoint.bind(this._model, breakpoint.sourceFileId, breakpoint.lineNumber); 143 contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeHandler); 144 145 contextMenu.show(event); 146 }, 147 148 _addListElement: function(element, beforeElement) 149 { 150 if (beforeElement) 151 this.listElement.insertBefore(element, beforeElement); 152 else { 153 if (!this.listElement.firstChild) { 154 this.bodyElement.removeChild(this.emptyElement); 155 this.bodyElement.appendChild(this.listElement); 156 } 157 this.listElement.appendChild(element); 158 } 159 }, 160 161 _removeListElement: function(element) 162 { 163 this.listElement.removeChild(element); 164 if (!this.listElement.firstChild) { 165 this.bodyElement.removeChild(this.listElement); 166 this.bodyElement.appendChild(this.emptyElement); 167 } 168 }, 169 170 _compare: function(x, y) 171 { 172 if (x !== y) 173 return x < y ? -1 : 1; 174 return 0; 175 }, 176 177 _compareBreakpoints: function(b1, b2) 178 { 179 return this._compare(b1.url, b2.url) || this._compare(b1.lineNumber, b2.lineNumber); 180 }, 181 182 reset: function() 183 { 184 this.listElement.removeChildren(); 185 if (this.listElement.parentElement) { 186 this.bodyElement.removeChild(this.listElement); 187 this.bodyElement.appendChild(this.emptyElement); 188 } 189 this._items = {}; 190 } 191 } 192 193 WebInspector.JavaScriptBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; 194 195 WebInspector.NativeBreakpointsSidebarPane = function(title) 196 { 197 WebInspector.SidebarPane.call(this, title); 198 199 this.listElement = document.createElement("ol"); 200 this.listElement.className = "breakpoint-list"; 201 202 this.emptyElement = document.createElement("div"); 203 this.emptyElement.className = "info"; 204 this.emptyElement.textContent = WebInspector.UIString("No Breakpoints"); 205 206 this.bodyElement.appendChild(this.emptyElement); 207 } 208 209 WebInspector.NativeBreakpointsSidebarPane.prototype = { 210 _addListElement: function(element, beforeElement) 211 { 212 if (beforeElement) 213 this.listElement.insertBefore(element, beforeElement); 214 else { 215 if (!this.listElement.firstChild) { 216 this.bodyElement.removeChild(this.emptyElement); 217 this.bodyElement.appendChild(this.listElement); 218 } 219 this.listElement.appendChild(element); 220 } 221 }, 222 223 _removeListElement: function(element) 224 { 225 this.listElement.removeChild(element); 226 if (!this.listElement.firstChild) { 227 this.bodyElement.removeChild(this.listElement); 228 this.bodyElement.appendChild(this.emptyElement); 229 } 230 }, 231 232 _reset: function() 233 { 234 this.listElement.removeChildren(); 235 if (this.listElement.parentElement) { 236 this.bodyElement.removeChild(this.listElement); 237 this.bodyElement.appendChild(this.emptyElement); 238 } 239 } 240 } 241 242 WebInspector.NativeBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; 243 244 WebInspector.XHRBreakpointsSidebarPane = function() 245 { 246 WebInspector.NativeBreakpointsSidebarPane.call(this, WebInspector.UIString("XHR Breakpoints")); 247 248 this._breakpointElements = {}; 249 250 var addButton = document.createElement("button"); 251 addButton.className = "add"; 252 addButton.addEventListener("click", this._addButtonClicked.bind(this), false); 253 this.titleElement.appendChild(addButton); 254 255 this._restoreBreakpoints(); 256 } 257 258 WebInspector.XHRBreakpointsSidebarPane.prototype = { 259 _addButtonClicked: function(event) 260 { 261 event.stopPropagation(); 262 263 this.expanded = true; 264 265 var inputElement = document.createElement("span"); 266 inputElement.className = "breakpoint-condition editing"; 267 this._addListElement(inputElement, this.listElement.firstChild); 268 269 function finishEditing(accept, e, text) 270 { 271 this._removeListElement(inputElement); 272 if (accept) { 273 this._setBreakpoint(text, true); 274 this._saveBreakpoints(); 275 } 276 } 277 278 WebInspector.startEditing(inputElement, { 279 commitHandler: finishEditing.bind(this, true), 280 cancelHandler: finishEditing.bind(this, false) 281 }); 282 }, 283 284 _setBreakpoint: function(url, enabled) 285 { 286 if (url in this._breakpointElements) 287 return; 288 289 var element = document.createElement("li"); 290 element._url = url; 291 element.addEventListener("contextmenu", this._contextMenu.bind(this, url), true); 292 293 var checkboxElement = document.createElement("input"); 294 checkboxElement.className = "checkbox-elem"; 295 checkboxElement.type = "checkbox"; 296 checkboxElement.checked = enabled; 297 checkboxElement.addEventListener("click", this._checkboxClicked.bind(this, url), false); 298 element._checkboxElement = checkboxElement; 299 element.appendChild(checkboxElement); 300 301 var labelElement = document.createElement("span"); 302 if (!url) 303 labelElement.textContent = WebInspector.UIString("Any XHR"); 304 else 305 labelElement.textContent = WebInspector.UIString("URL contains \"%s\"", url); 306 labelElement.addStyleClass("cursor-auto"); 307 labelElement.addEventListener("dblclick", this._labelClicked.bind(this, url), false); 308 element.appendChild(labelElement); 309 310 var currentElement = this.listElement.firstChild; 311 while (currentElement) { 312 if (currentElement._url && currentElement._url < element._url) 313 break; 314 currentElement = currentElement.nextSibling; 315 } 316 this._addListElement(element, currentElement); 317 this._breakpointElements[url] = element; 318 if (enabled) 319 BrowserDebuggerAgent.setXHRBreakpoint(url); 320 }, 321 322 _removeBreakpoint: function(url) 323 { 324 var element = this._breakpointElements[url]; 325 if (!element) 326 return; 327 328 this._removeListElement(element); 329 delete this._breakpointElements[url]; 330 if (element._checkboxElement.checked) 331 BrowserDebuggerAgent.removeXHRBreakpoint(url); 332 }, 333 334 _contextMenu: function(url, event) 335 { 336 var contextMenu = new WebInspector.ContextMenu(); 337 function removeBreakpoint() 338 { 339 this._removeBreakpoint(url); 340 this._saveBreakpoints(); 341 } 342 contextMenu.appendItem(WebInspector.UIString("Remove Breakpoint"), removeBreakpoint.bind(this)); 343 contextMenu.show(event); 344 }, 345 346 _checkboxClicked: function(url, event) 347 { 348 if (event.target.checked) 349 BrowserDebuggerAgent.setXHRBreakpoint(url); 350 else 351 BrowserDebuggerAgent.removeXHRBreakpoint(url); 352 this._saveBreakpoints(); 353 }, 354 355 _labelClicked: function(url) 356 { 357 var element = this._breakpointElements[url]; 358 var inputElement = document.createElement("span"); 359 inputElement.className = "breakpoint-condition editing"; 360 inputElement.textContent = url; 361 this.listElement.insertBefore(inputElement, element); 362 element.addStyleClass("hidden"); 363 364 function finishEditing(accept, e, text) 365 { 366 this._removeListElement(inputElement); 367 if (accept) { 368 this._removeBreakpoint(url); 369 this._setBreakpoint(text, element._checkboxElement.checked); 370 this._saveBreakpoints(); 371 } else 372 element.removeStyleClass("hidden"); 373 } 374 375 WebInspector.startEditing(inputElement, { 376 commitHandler: finishEditing.bind(this, true), 377 cancelHandler: finishEditing.bind(this, false) 378 }); 379 }, 380 381 highlightBreakpoint: function(url) 382 { 383 var element = this._breakpointElements[url]; 384 if (!element) 385 return; 386 this.expanded = true; 387 element.addStyleClass("breakpoint-hit"); 388 this._highlightedElement = element; 389 }, 390 391 clearBreakpointHighlight: function() 392 { 393 if (this._highlightedElement) { 394 this._highlightedElement.removeStyleClass("breakpoint-hit"); 395 delete this._highlightedElement; 396 } 397 }, 398 399 _saveBreakpoints: function() 400 { 401 var breakpoints = []; 402 for (var url in this._breakpointElements) 403 breakpoints.push({ url: url, enabled: this._breakpointElements[url]._checkboxElement.checked }); 404 WebInspector.settings.xhrBreakpoints = breakpoints; 405 }, 406 407 _restoreBreakpoints: function() 408 { 409 var breakpoints = WebInspector.settings.xhrBreakpoints; 410 for (var i = 0; i < breakpoints.length; ++i) { 411 var breakpoint = breakpoints[i]; 412 if (breakpoint && typeof breakpoint.url === "string") 413 this._setBreakpoint(breakpoint.url, breakpoint.enabled); 414 } 415 } 416 } 417 418 WebInspector.XHRBreakpointsSidebarPane.prototype.__proto__ = WebInspector.NativeBreakpointsSidebarPane.prototype; 419 420 WebInspector.EventListenerBreakpointsSidebarPane = function() 421 { 422 WebInspector.SidebarPane.call(this, WebInspector.UIString("Event Listener Breakpoints")); 423 424 this.categoriesElement = document.createElement("ol"); 425 this.categoriesElement.tabIndex = 0; 426 this.categoriesElement.addStyleClass("properties-tree"); 427 this.categoriesElement.addStyleClass("event-listener-breakpoints"); 428 this.categoriesTreeOutline = new TreeOutline(this.categoriesElement); 429 this.bodyElement.appendChild(this.categoriesElement); 430 431 this._breakpointItems = {}; 432 this._createCategory(WebInspector.UIString("Keyboard"), "listener", ["keydown", "keyup", "keypress", "textInput"]); 433 this._createCategory(WebInspector.UIString("Mouse"), "listener", ["click", "dblclick", "mousedown", "mouseup", "mouseover", "mousemove", "mouseout", "mousewheel"]); 434 // FIXME: uncomment following once inspector stops being drop targer in major ports. 435 // Otherwise, inspector page reacts on drop event and tries to load the event data. 436 // this._createCategory(WebInspector.UIString("Drag"), "listener", ["drag", "drop", "dragstart", "dragend", "dragenter", "dragleave", "dragover"]); 437 this._createCategory(WebInspector.UIString("Control"), "listener", ["resize", "scroll", "zoom", "focus", "blur", "select", "change", "submit", "reset"]); 438 this._createCategory(WebInspector.UIString("Clipboard"), "listener", ["copy", "cut", "paste", "beforecopy", "beforecut", "beforepaste"]); 439 this._createCategory(WebInspector.UIString("Load"), "listener", ["load", "unload", "abort", "error"]); 440 this._createCategory(WebInspector.UIString("DOM Mutation"), "listener", ["DOMActivate", "DOMFocusIn", "DOMFocusOut", "DOMAttrModified", "DOMCharacterDataModified", "DOMNodeInserted", "DOMNodeInsertedIntoDocument", "DOMNodeRemoved", "DOMNodeRemovedFromDocument", "DOMSubtreeModified", "DOMContentLoaded"]); 441 this._createCategory(WebInspector.UIString("Device"), "listener", ["deviceorientation", "devicemotion"]); 442 this._createCategory(WebInspector.UIString("Timer"), "instrumentation", ["setTimer", "clearTimer", "timerFired"]); 443 444 this._restoreBreakpoints(); 445 } 446 447 WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI = function(eventName) 448 { 449 if (!WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI) { 450 WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI = { 451 "instrumentation:setTimer": WebInspector.UIString("Set Timer"), 452 "instrumentation:clearTimer": WebInspector.UIString("Clear Timer"), 453 "instrumentation:timerFired": WebInspector.UIString("Timer Fired") 454 }; 455 } 456 return WebInspector.EventListenerBreakpointsSidebarPane._eventNamesForUI[eventName] || eventName.substring(eventName.indexOf(":") + 1); 457 } 458 459 WebInspector.EventListenerBreakpointsSidebarPane.prototype = { 460 _createCategory: function(name, type, eventNames) 461 { 462 var categoryItem = {}; 463 categoryItem.element = new TreeElement(name); 464 this.categoriesTreeOutline.appendChild(categoryItem.element); 465 categoryItem.element.listItemElement.addStyleClass("event-category"); 466 categoryItem.element.selectable = true; 467 468 categoryItem.checkbox = this._createCheckbox(categoryItem.element); 469 categoryItem.checkbox.addEventListener("click", this._categoryCheckboxClicked.bind(this, categoryItem), true); 470 471 categoryItem.children = {}; 472 for (var i = 0; i < eventNames.length; ++i) { 473 var eventName = type + ":" + eventNames[i]; 474 475 var breakpointItem = {}; 476 var title = WebInspector.EventListenerBreakpointsSidebarPane.eventNameForUI(eventName); 477 breakpointItem.element = new TreeElement(title); 478 categoryItem.element.appendChild(breakpointItem.element); 479 var hitMarker = document.createElement("div"); 480 hitMarker.className = "breakpoint-hit-marker"; 481 breakpointItem.element.listItemElement.appendChild(hitMarker); 482 breakpointItem.element.listItemElement.addStyleClass("source-code"); 483 breakpointItem.element.selectable = true; 484 485 breakpointItem.checkbox = this._createCheckbox(breakpointItem.element); 486 breakpointItem.checkbox.addEventListener("click", this._breakpointCheckboxClicked.bind(this, eventName), true); 487 breakpointItem.parent = categoryItem; 488 489 this._breakpointItems[eventName] = breakpointItem; 490 categoryItem.children[eventName] = breakpointItem; 491 } 492 }, 493 494 _createCheckbox: function(treeElement) 495 { 496 var checkbox = document.createElement("input"); 497 checkbox.className = "checkbox-elem"; 498 checkbox.type = "checkbox"; 499 treeElement.listItemElement.insertBefore(checkbox, treeElement.listItemElement.firstChild); 500 return checkbox; 501 }, 502 503 _categoryCheckboxClicked: function(categoryItem) 504 { 505 var checked = categoryItem.checkbox.checked; 506 for (var eventName in categoryItem.children) { 507 var breakpointItem = categoryItem.children[eventName]; 508 if (breakpointItem.checkbox.checked === checked) 509 continue; 510 if (checked) 511 this._setBreakpoint(eventName); 512 else 513 this._removeBreakpoint(eventName); 514 } 515 this._saveBreakpoints(); 516 }, 517 518 _breakpointCheckboxClicked: function(eventName, event) 519 { 520 if (event.target.checked) 521 this._setBreakpoint(eventName); 522 else 523 this._removeBreakpoint(eventName); 524 this._saveBreakpoints(); 525 }, 526 527 _setBreakpoint: function(eventName) 528 { 529 var breakpointItem = this._breakpointItems[eventName]; 530 if (!breakpointItem) 531 return; 532 breakpointItem.checkbox.checked = true; 533 BrowserDebuggerAgent.setEventListenerBreakpoint(eventName); 534 this._updateCategoryCheckbox(breakpointItem.parent); 535 }, 536 537 _removeBreakpoint: function(eventName) 538 { 539 var breakpointItem = this._breakpointItems[eventName]; 540 if (!breakpointItem) 541 return; 542 breakpointItem.checkbox.checked = false; 543 BrowserDebuggerAgent.removeEventListenerBreakpoint(eventName); 544 this._updateCategoryCheckbox(breakpointItem.parent); 545 }, 546 547 _updateCategoryCheckbox: function(categoryItem) 548 { 549 var hasEnabled = false, hasDisabled = false; 550 for (var eventName in categoryItem.children) { 551 var breakpointItem = categoryItem.children[eventName]; 552 if (breakpointItem.checkbox.checked) 553 hasEnabled = true; 554 else 555 hasDisabled = true; 556 } 557 categoryItem.checkbox.checked = hasEnabled; 558 categoryItem.checkbox.indeterminate = hasEnabled && hasDisabled; 559 }, 560 561 highlightBreakpoint: function(eventName) 562 { 563 var breakpointItem = this._breakpointItems[eventName]; 564 if (!breakpointItem) 565 return; 566 this.expanded = true; 567 breakpointItem.parent.element.expand(); 568 breakpointItem.element.listItemElement.addStyleClass("breakpoint-hit"); 569 this._highlightedElement = breakpointItem.element.listItemElement; 570 }, 571 572 clearBreakpointHighlight: function() 573 { 574 if (this._highlightedElement) { 575 this._highlightedElement.removeStyleClass("breakpoint-hit"); 576 delete this._highlightedElement; 577 } 578 }, 579 580 _saveBreakpoints: function() 581 { 582 var breakpoints = []; 583 for (var eventName in this._breakpointItems) { 584 if (this._breakpointItems[eventName].checkbox.checked) 585 breakpoints.push({ eventName: eventName }); 586 } 587 WebInspector.settings.eventListenerBreakpoints = breakpoints; 588 }, 589 590 _restoreBreakpoints: function() 591 { 592 var breakpoints = WebInspector.settings.eventListenerBreakpoints; 593 for (var i = 0; i < breakpoints.length; ++i) { 594 var breakpoint = breakpoints[i]; 595 if (breakpoint && typeof breakpoint.eventName === "string") 596 this._setBreakpoint(breakpoint.eventName); 597 } 598 } 599 } 600 601 WebInspector.EventListenerBreakpointsSidebarPane.prototype.__proto__ = WebInspector.SidebarPane.prototype; 602