1 /* See license.txt for terms of usage */ 2 3 FBL.ns(function() { with (FBL) { 4 5 // ************************************************************************************************ 6 // Constants 7 8 const Cc = Components.classes; 9 const Ci = Components.interfaces; 10 const jsdIStackFrame = Ci.jsdIStackFrame; 11 12 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 13 14 const insertSliceSize = 18; 15 const insertInterval = 40; 16 17 const rxIdentifier = /^[$_A-Za-z][$_A-Za-z0-9]*$/ 18 19 const ignoreVars = 20 { 21 "__firebug__": 1, 22 "eval": 1, 23 24 // We are forced to ignore Java-related variables, because 25 // trying to access them causes browser freeze 26 "java": 1, 27 "sun": 1, 28 "Packages": 1, 29 "JavaArray": 1, 30 "JavaMember": 1, 31 "JavaObject": 1, 32 "JavaClass": 1, 33 "JavaPackage": 1, 34 // internal firebug things 35 "_firebug": 1, 36 "_FirebugConsole": 1, 37 "_FirebugCommandLine": 1, 38 "loadFirebugConsole": 1, 39 "_getFirebugConsoleElement": 1, 40 }; 41 42 // ************************************************************************************************ 43 44 Firebug.DOMModule = extend(Firebug.Module, 45 { 46 initialize: function(prefDomain, prefNames) 47 { 48 Firebug.Module.initialize.apply(this, arguments); 49 Firebug.Debugger.addListener(this.DebuggerListener); 50 }, 51 52 initContext: function(context, persistedState) 53 { 54 Firebug.Module.initContext.apply(this, arguments); 55 context.dom = {breakpoints: new DOMBreakpointGroup()}; 56 }, 57 58 loadedContext: function(context, persistedState) 59 { 60 context.dom.breakpoints.load(context); 61 }, 62 63 destroyContext: function(context, persistedState) 64 { 65 Firebug.Module.destroyContext.apply(this, arguments); 66 67 context.dom.breakpoints.store(context); 68 }, 69 70 shutdown: function() 71 { 72 Firebug.Module.shutdown.apply(this, arguments); 73 Firebug.Debugger.removeListener(this.DebuggerListener); 74 }, 75 }); 76 77 // ************************************************************************************************ 78 79 const WatchRowTag = 80 TR({"class": "watchNewRow", level: 0}, 81 TD({"class": "watchEditCell", colspan: 3}, 82 DIV({"class": "watchEditBox a11yFocusNoTab", role: "button", 'tabindex' : '0', 83 'aria-label' : $STR('a11y.labels.press enter to add new watch expression')}, 84 $STR("NewWatch") 85 ) 86 ) 87 ); 88 89 const SizerRow = 90 TR({role : 'presentation'}, 91 TD(), 92 TD({width: "30%"}), 93 TD({width: "70%"}) 94 ); 95 96 const DirTablePlate = domplate(Firebug.Rep, 97 { 98 memberRowTag: 99 TR({"class": "memberRow $member.open $member.type\\Row", _domObject: "$member", 100 $hasChildren: "$member.hasChildren", 101 role: "presentation", 102 level: "$member.level", 103 breakable: "$member.breakable", 104 breakpoint: "$member.breakpoint", 105 disabledBreakpoint: "$member.disabledBreakpoint"}, 106 TD({"class": "memberHeaderCell"}, 107 DIV({"class": "sourceLine memberRowHeader", onclick: "$onClickRowHeader"}, 108 " " 109 ) 110 ), 111 TD({"class": "memberLabelCell", style: "padding-left: $member.indent\\px", 112 role: 'presentation'}, 113 DIV({"class": "memberLabel $member.type\\Label"}, 114 SPAN({"class": "memberLabelPrefix"}, "$member.prefix"), 115 SPAN("$member.name") 116 ) 117 ), 118 TD({"class": "memberValueCell", role : 'presentation'}, 119 TAG("$member.tag", {object: "$member.value"}) 120 ) 121 ), 122 123 tag: 124 TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, onclick: "$onClick", 125 role: "tree", 'aria-label': $STR('aria.labels.dom properties')}, 126 TBODY({role: 'presentation'}, 127 SizerRow, 128 FOR("member", "$object|memberIterator", 129 TAG("$memberRowTag", {member: "$member"}) 130 ) 131 ) 132 ), 133 134 watchTag: 135 TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, 136 _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'}, 137 TBODY({role : 'presentation'}, 138 SizerRow, 139 WatchRowTag 140 ) 141 ), 142 143 tableTag: 144 TABLE({"class": "domTable", cellpadding: 0, cellspacing: 0, 145 _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", 146 role: 'tree', 'aria-label': 'DOM properties'}, 147 TBODY({role : 'presentation'}, 148 SizerRow 149 ) 150 ), 151 152 rowTag: 153 FOR("member", "$members", 154 TAG("$memberRowTag", {member: "$member"}) 155 ), 156 157 memberIterator: function(object, level) 158 { 159 return Firebug.DOMBasePanel.prototype.getMembers(object, level, this.context); 160 }, 161 162 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 163 164 onClick: function(event) 165 { 166 if (!isLeftClick(event)) 167 return; 168 169 var row = getAncestorByClass(event.target, "memberRow"); 170 var label = getAncestorByClass(event.target, "memberLabel"); 171 var valueCell = row.getElementsByClassName("memberValueCell").item(0); 172 var object = Firebug.getRepObject(event.target); 173 var target = row.lastChild.firstChild; 174 var isString = hasClass(target,"objectBox-string"); 175 var inValueCell = event.target == valueCell || event.target == target; 176 177 if (label && hasClass(row, "hasChildren") && !(isString && inValueCell)) 178 { 179 var row = label.parentNode.parentNode; 180 this.toggleRow(row); 181 cancelEvent(event); 182 } 183 else 184 { 185 if (typeof(object) == "function") 186 { 187 Firebug.chrome.select(object, "script"); 188 cancelEvent(event); 189 } 190 else if (event.detail == 2 && !object) 191 { 192 var panel = row.parentNode.parentNode.domPanel; 193 if (panel) 194 { 195 var rowValue = panel.getRowPropertyValue(row); 196 if (typeof(rowValue) == "boolean") 197 panel.setPropertyValue(row, !rowValue); 198 else 199 panel.editProperty(row); 200 201 cancelEvent(event); 202 } 203 } 204 } 205 }, 206 207 toggleRow: function(row) 208 { 209 var level = parseInt(row.getAttribute("level")); 210 var toggles = row.parentNode.parentNode.toggles; 211 212 var panel = row.parentNode.parentNode.domPanel; 213 var target = row.lastChild.firstChild; 214 var isString = hasClass(target,"objectBox-string"); 215 216 if (hasClass(row, "opened")) 217 { 218 removeClass(row, "opened"); 219 220 if (isString) 221 { 222 var rowValue = panel.getRowPropertyValue(row); 223 row.lastChild.firstChild.textContent = '"' + cropMultipleLines(rowValue) + '"'; 224 } 225 else 226 { 227 if (toggles) 228 { 229 var path = getPath(row); 230 231 // Remove the path from the toggle tree 232 for (var i = 0; i < path.length; ++i) 233 { 234 if (i == path.length-1) 235 delete toggles[path[i]]; 236 else 237 toggles = toggles[path[i]]; 238 } 239 } 240 241 var rowTag = this.rowTag; 242 var tbody = row.parentNode; 243 244 setTimeout(function() 245 { 246 for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling) 247 { 248 if (parseInt(firstRow.getAttribute("level")) <= level) 249 break; 250 251 tbody.removeChild(firstRow); 252 } 253 }, row.insertTimeout ? row.insertTimeout : 0); 254 } 255 } 256 else 257 { 258 setClass(row, "opened"); 259 if (isString) 260 { 261 var rowValue = panel.getRowPropertyValue(row); 262 row.lastChild.firstChild.textContent = '"' + rowValue + '"'; 263 } 264 else 265 { 266 267 if (toggles) 268 { 269 var path = getPath(row); 270 271 // Mark the path in the toggle tree 272 for (var i = 0; i < path.length; ++i) 273 { 274 var name = path[i]; 275 if (toggles.hasOwnProperty(name)) 276 toggles = toggles[name]; 277 else 278 toggles = toggles[name] = {}; 279 } 280 } 281 282 var context = panel ? panel.context : null; 283 var members = Firebug.DOMBasePanel.prototype.getMembers(target.repObject, level+1, context); 284 285 var rowTag = this.rowTag; 286 var lastRow = row; 287 288 var delay = 0; 289 var setSize = members.length; 290 var rowCount = 1; 291 while (members.length) 292 { 293 setTimeout(function(slice, isLast) 294 { 295 if (lastRow.parentNode) 296 { 297 var result = rowTag.insertRows({members: slice}, lastRow); 298 lastRow = result[1]; 299 dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [null, result, rowCount, setSize]); 300 rowCount += insertSliceSize; 301 } 302 if (isLast) 303 delete row.insertTimeout; 304 }, delay, members.splice(0, insertSliceSize), !members.length); 305 306 delay += insertInterval; 307 } 308 309 row.insertTimeout = delay; 310 } 311 } 312 }, 313 314 onClickRowHeader: function(event) 315 { 316 cancelEvent(event); 317 318 var rowHeader = event.target; 319 if (!hasClass(rowHeader, "memberRowHeader")) 320 return; 321 322 var row = getAncestorByClass(event.target, "memberRow"); 323 if (!row) 324 return; 325 326 var panel = row.parentNode.parentNode.domPanel; 327 if (panel) 328 panel.breakOnProperty(row); 329 } 330 }); 331 332 const ToolboxPlate = domplate( 333 { 334 tag: 335 DIV({"class": "watchToolbox", _domPanel: "$domPanel", onclick: "$onClick"}, 336 IMG({"class": "watchDeleteButton closeButton", src: "blank.gif"}) 337 ), 338 339 onClick: function(event) 340 { 341 var toolbox = event.currentTarget; 342 toolbox.domPanel.deleteWatch(toolbox.watchRow); 343 } 344 }); 345 346 // ************************************************************************************************ 347 348 Firebug.DOMBasePanel = function() {} 349 350 Firebug.DOMBasePanel.prototype = extend(Firebug.ActivablePanel, 351 { 352 tag: DirTablePlate.tableTag, 353 354 getRealObject: function(object) 355 { 356 return unwrapObject(object); 357 }, 358 359 rebuild: function(update, scrollTop) 360 { 361 dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]); 362 var members = this.getMembers(this.selection, 0, this.context); 363 this.expandMembers(members, this.toggles, 0, 0, this.context); 364 365 this.showMembers(members, update, scrollTop); 366 }, 367 /* 368 * @param object a user-level object wrapped in security blanket 369 * @param level for a.b.c, level is 2 370 * @param context 371 */ 372 getMembers: function(object, level, context) 373 { 374 if (!level) 375 level = 0; 376 377 var ordinals = [], userProps = [], userClasses = [], userFuncs = [], 378 domProps = [], domFuncs = [], domConstants = []; 379 380 try 381 { 382 // Special case for "arguments", which is not enumerable by for...in statement. 383 if (isArguments(object)) 384 object = cloneArray(object); 385 386 var domMembers = getDOMMembers(object); 387 var insecureObject = unwrapObject(object); 388 389 for (var name in insecureObject) // enumeration is safe 390 { 391 // Ignore only global variables (properties of the |window| object). 392 // javascript.options.strict says ignoreVars is undefined. 393 if (ignoreVars[name] == 1 && (object instanceof Window)) 394 { 395 if (FBTrace.DBG_DOM) 396 FBTrace.sysout("dom.getMembers: ignoreVars: " + name + ", " + level, object); 397 continue; 398 } 399 400 var val; 401 try 402 { 403 val = insecureObject[name]; // getter is safe 404 } 405 catch (exc) 406 { 407 // Sometimes we get exceptions trying to access certain members 408 if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM) 409 FBTrace.sysout("dom.getMembers cannot access "+name, exc); 410 } 411 412 var ordinal = parseInt(name); 413 if (ordinal || ordinal == 0) 414 { 415 addMember(object, "ordinal", ordinals, name, val, level, 0, context); 416 } 417 else if (typeof(val) == "function") 418 { 419 if (isClassFunction(val)) 420 addMember(object, "userClass", userClasses, name, val, level, 0, context); 421 else if (name in domMembers) 422 addMember(object, "domFunction", domFuncs, name, val, level, domMembers[name], context); 423 else 424 addMember(object, "userFunction", userFuncs, name, val, level, 0, context); 425 } 426 else 427 { 428 if (name in domMembers) 429 addMember(object, "dom", domProps, name, val, level, domMembers[name], context); 430 else if (name in domConstantMap) 431 addMember(object, "dom", domConstants, name, val, level, 0, context); 432 else 433 addMember(object, "user", userProps, name, val, level, 0, context); 434 } 435 } 436 } 437 catch (exc) 438 { 439 // Sometimes we get exceptions just from trying to iterate the members 440 // of certain objects, like StorageList, but don't let that gum up the works 441 //throw exc; 442 if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM) 443 FBTrace.sysout("dom.getMembers FAILS: ", exc); 444 } 445 446 function sortName(a, b) { return a.name > b.name ? 1 : -1; } 447 function sortOrder(a, b) { return a.order > b.order ? 1 : -1; } 448 449 var members = []; 450 451 members.push.apply(members, ordinals); 452 453 if (Firebug.showUserProps) 454 { 455 userProps.sort(sortName); 456 members.push.apply(members, userProps); 457 } 458 459 if (Firebug.showUserFuncs) 460 { 461 userClasses.sort(sortName); 462 members.push.apply(members, userClasses); 463 464 userFuncs.sort(sortName); 465 members.push.apply(members, userFuncs); 466 } 467 468 if (Firebug.showDOMProps) 469 { 470 domProps.sort(sortName); 471 members.push.apply(members, domProps); 472 } 473 474 if (Firebug.showDOMFuncs) 475 { 476 domFuncs.sort(sortName); 477 members.push.apply(members, domFuncs); 478 } 479 480 if (Firebug.showDOMConstants) 481 members.push.apply(members, domConstants); 482 483 return members; 484 }, 485 486 expandMembers: function (members, toggles, offset, level, context) // recursion starts with offset=0, level=0 487 { 488 var expanded = 0; 489 for (var i = offset; i < members.length; ++i) 490 { 491 var member = members[i]; 492 if (member.level > level) 493 break; 494 495 if ( toggles.hasOwnProperty(member.name) ) 496 { 497 member.open = "opened"; // member.level <= level && member.name in toggles. 498 if (member.type == 'string') 499 continue; 500 var newMembers = this.getMembers(member.value, level+1, context); // sets newMembers.level to level+1 501 502 var args = [i+1, 0]; 503 args.push.apply(args, newMembers); 504 members.splice.apply(members, args); 505 if (FBTrace.DBG_DOM) 506 { 507 FBTrace.sysout("expandMembers member.name", member.name); 508 FBTrace.sysout("expandMembers toggles", toggles); 509 FBTrace.sysout("expandMembers toggles[member.name]", toggles[member.name]); 510 FBTrace.sysout("dom.expandedMembers level: "+level+" member", member); 511 } 512 513 expanded += newMembers.length; 514 i += newMembers.length + this.expandMembers(members, toggles[member.name], i+1, level+1, context); 515 } 516 } 517 518 return expanded; 519 }, 520 521 showMembers: function(members, update, scrollTop) 522 { 523 // If we are still in the midst of inserting rows, cancel all pending 524 // insertions here - this is a big speedup when stepping in the debugger 525 if (this.timeouts) 526 { 527 for (var i = 0; i < this.timeouts.length; ++i) 528 this.context.clearTimeout(this.timeouts[i]); 529 delete this.timeouts; 530 } 531 532 if (!members.length) 533 return this.showEmptyMembers(); 534 535 var panelNode = this.panelNode; 536 var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop; 537 538 // If we are asked to "update" the current view, then build the new table 539 // offscreen and swap it in when it's done 540 var offscreen = update && panelNode.firstChild; 541 var dest = offscreen ? this.document : panelNode; 542 543 var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest); 544 var tbody = table.lastChild; 545 var rowTag = DirTablePlate.rowTag; 546 547 // Insert the first slice immediately 548 var setSize = members.length; 549 var slice = members.splice(0, insertSliceSize); 550 var result = rowTag.insertRows({members: slice}, tbody.lastChild); 551 var rowCount = 1; 552 var panel = this; 553 dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); 554 var timeouts = []; 555 556 var delay = 0; 557 while (members.length) 558 { 559 timeouts.push(this.context.setTimeout(function(slice) 560 { 561 result = rowTag.insertRows({members: slice}, tbody.lastChild); 562 rowCount += insertSliceSize; 563 dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]); 564 565 if ((panelNode.scrollHeight+panelNode.offsetHeight) >= priorScrollTop) 566 panelNode.scrollTop = priorScrollTop; 567 }, delay, members.splice(0, insertSliceSize))); 568 569 delay += insertInterval; 570 } 571 572 if (offscreen) 573 { 574 timeouts.push(this.context.setTimeout(function() 575 { 576 if (panelNode.firstChild) 577 panelNode.replaceChild(table, panelNode.firstChild); 578 else 579 panelNode.appendChild(table); 580 581 // Scroll back to where we were before 582 panelNode.scrollTop = priorScrollTop; 583 }, delay)); 584 } 585 else 586 { 587 timeouts.push(this.context.setTimeout(function() 588 { 589 panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop; 590 }, delay)); 591 } 592 this.timeouts = timeouts; 593 }, 594 595 showEmptyMembers: function() 596 { 597 FirebugReps.Warning.tag.replace({object: "NoMembersWarning"}, this.panelNode); 598 }, 599 600 findPathIndex: function(object) 601 { 602 var pathIndex = -1; 603 for (var i = 0; i < this.objectPath.length; ++i) 604 { 605 if (this.getPathObject(i) == object) 606 return i; 607 } 608 609 return -1; 610 }, 611 612 getPathObject: function(index) 613 { 614 var object = this.objectPath[index]; 615 if (object instanceof Property) 616 return object.getObject(); 617 else 618 return object; 619 }, 620 621 getRowObject: function(row) 622 { 623 var object = getRowOwnerObject(row); 624 return object ? object : this.selection; 625 }, 626 627 getRealRowObject: function(row) 628 { 629 var object = this.getRowObject(row); 630 return this.getRealObject(object); 631 }, 632 633 getRowPropertyValue: function(row) 634 { 635 var object = this.getRealRowObject(row); 636 return this.getObjectPropertyValue(object, row.domObject.name); 637 }, 638 639 getObjectPropertyValue: function(object, propName) 640 { 641 if (!object) 642 return; 643 644 // Get the value with try-catch statement. This method is used also wihin 645 // getContextMenuItems where the exception would break the context menu. 646 // 1) The Firebug.Debugger.evaluate can throw 647 // 2) object[propName] can also throws in case of e.g. non existing "abc.abc" prop name. 648 try 649 { 650 if (object instanceof jsdIStackFrame) 651 return Firebug.Debugger.evaluate(propName, this.context); 652 else 653 return object[propName]; 654 } 655 catch (err) 656 { 657 if(FBTrace.DBG_DOM || FBTrace.DBG_ERRORS) 658 FBTrace.sysout("dom.getObjectPropertyValue; EXCEPTION " + propName, object); 659 } 660 }, 661 662 getRowPathName: function(row) 663 { 664 var name = row.domObject.name; 665 var seperator = ""; 666 667 if(name.match(/^[\d]+$/))//ordinal 668 return ["", "["+name+"]"]; 669 else if(name.match(rxIdentifier))//identifier 670 return [".", name]; 671 else//map keys 672 return ["", "[\""+name.replace(/\\/g, "\\\\").replace(/"/g,"\\\"") + "\"]"]; 673 }, 674 675 copyName: function(row) 676 { 677 var value = this.getRowPathName(row); 678 value = value[1];//don't want the seperator 679 copyToClipboard(value); 680 }, 681 682 copyPath: function(row) 683 { 684 var path = this.getPropertyPath(row); 685 copyToClipboard(path.join("")); 686 }, 687 688 /* 689 * Walk from the current row up to the most ancient parent, building an array. 690 * @return array of property names and separators, eg ['foo','.','bar']. 691 */ 692 getPropertyPath: function(row) 693 { 694 var path = []; 695 for(var current = row; current ; current = getParentRow(current)) 696 path = this.getRowPathName(current).concat(path); 697 path.splice(0,1); //don't want the first seperator 698 return path; 699 }, 700 701 copyProperty: function(row) 702 { 703 var value = this.getRowPropertyValue(row); 704 copyToClipboard(value); 705 }, 706 707 editProperty: function(row, editValue) 708 { 709 if (hasClass(row, "watchNewRow")) 710 { 711 if (this.context.stopped) 712 Firebug.Editor.startEditing(row, ""); 713 else if (Firebug.Console.isAlwaysEnabled()) // not stopped in debugger, need command line 714 { 715 if (Firebug.CommandLine.onCommandLineFocus()) 716 Firebug.Editor.startEditing(row, ""); 717 else 718 row.innerHTML = $STR("warning.Command line blocked?"); 719 } 720 else 721 row.innerHTML = $STR("warning.Console must be enabled"); 722 } 723 else if (hasClass(row, "watchRow")) 724 { 725 Firebug.Editor.startEditing(row, getRowName(row)); 726 } 727 else 728 { 729 var object = this.getRowObject(row); 730 this.context.thisValue = object; 731 732 if (!editValue) 733 { 734 var propValue = this.getRowPropertyValue(row); 735 736 var type = typeof(propValue); 737 if (type == "undefined" || type == "number" || type == "boolean") 738 editValue = propValue; 739 else if (type == "string") 740 editValue = "\"" + escapeJS(propValue) + "\""; 741 else if (propValue == null) 742 editValue = "null"; 743 else if (object instanceof Window || object instanceof jsdIStackFrame) 744 editValue = getRowName(row); 745 else 746 editValue = "this." + getRowName(row); 747 } 748 749 Firebug.Editor.startEditing(row, editValue); 750 } 751 }, 752 753 deleteProperty: function(row) 754 { 755 if (hasClass(row, "watchRow")) 756 this.deleteWatch(row); 757 else 758 { 759 var object = getRowOwnerObject(row); 760 if (!object) 761 object = this.selection; 762 object = this.getRealObject(object); 763 764 if (object) 765 { 766 var name = getRowName(row); 767 try 768 { 769 delete object[name]; 770 } 771 catch (exc) 772 { 773 return; 774 } 775 776 this.rebuild(true); 777 this.markChange(); 778 } 779 } 780 }, 781 782 setPropertyValue: function(row, value) // value must be string 783 { 784 if(FBTrace.DBG_DOM) 785 { 786 FBTrace.sysout("row: "+row); 787 FBTrace.sysout("value: "+value+" type "+typeof(value), value); 788 } 789 790 var name = getRowName(row); 791 if (name == "this") 792 return; 793 794 var object = this.getRealRowObject(row); 795 if (object && !(object instanceof jsdIStackFrame)) 796 { 797 // unwrappedJSObject.property = unwrappedJSObject 798 Firebug.CommandLine.evaluate(value, this.context, object, this.context.getGlobalScope(), 799 function success(result, context) 800 { 801 if (FBTrace.DBG_DOM) 802 FBTrace.sysout("setPropertyValue evaluate success object["+name+"]="+result+" type "+typeof(result), result); 803 object[name] = result; 804 }, 805 function failed(exc, context) 806 { 807 try 808 { 809 if (FBTrace.DBG_DOM) 810 FBTrace.sysout("setPropertyValue evaluate failed with exc:"+exc+" object["+name+"]="+value+" type "+typeof(value), exc); 811 // If the value doesn't parse, then just store it as a string. Some users will 812 // not realize they're supposed to enter a JavaScript expression and just type 813 // literal text 814 object[name] = String(value); // unwrappedJSobject.property = string 815 } 816 catch (exc) 817 { 818 return; 819 } 820 } 821 ); 822 } 823 else if (this.context.stopped) 824 { 825 try 826 { 827 Firebug.CommandLine.evaluate(name+"="+value, this.context); 828 } 829 catch (exc) 830 { 831 try 832 { 833 // See catch block above... 834 object[name] = String(value); // unwrappedJSobject.property = string 835 } 836 catch (exc) 837 { 838 return; 839 } 840 } 841 } 842 843 this.rebuild(true); 844 this.markChange(); 845 }, 846 847 highlightRow: function(row) 848 { 849 if (this.highlightedRow) 850 cancelClassTimed(this.highlightedRow, "jumpHighlight", this.context); 851 852 this.highlightedRow = row; 853 854 if (row) 855 setClassTimed(row, "jumpHighlight", this.context); 856 }, 857 858 breakOnProperty: function(row) 859 { 860 var member = row.domObject; 861 if (!member) 862 return; 863 864 // Bail out if this property is not breakable. 865 if (!member.breakable) 866 return; 867 868 //xxxHonza: don't use getRowName to get the prop name. From some reason 869 // unwatch doesn't work if row.firstChild.textContent is used. 870 // It works only from within the watch handler method if the passed param 871 // name is used. 872 var name = member.name; 873 if (name == "this") 874 return; 875 876 var object = this.getRowObject(row); 877 object = this.getRealObject(object); 878 if (!object) 879 return; 880 881 // Create new or remove an existing breakpoint. 882 var breakpoints = this.context.dom.breakpoints; 883 var bp = breakpoints.findBreakpoint(object, name); 884 if (bp) 885 { 886 row.removeAttribute("breakpoint"); 887 breakpoints.removeBreakpoint(object, name); 888 } 889 else 890 { 891 breakpoints.addBreakpoint(object, name, this, row); 892 row.setAttribute("breakpoint", "true"); 893 } 894 }, 895 896 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 897 // extends Panel 898 899 initialize: function() 900 { 901 this.objectPath = []; 902 this.propertyPath = []; 903 this.viewPath = []; 904 this.pathIndex = -1; 905 this.toggles = {}; 906 907 Firebug.Panel.initialize.apply(this, arguments); 908 }, 909 910 destroy: function(state) 911 { 912 var view = this.viewPath[this.pathIndex]; 913 if (view && this.panelNode.scrollTop) 914 view.scrollTop = this.panelNode.scrollTop; 915 916 if (this.pathIndex > -1) 917 state.pathIndex = this.pathIndex; 918 if (this.viewPath) 919 state.viewPath = this.viewPath; 920 if (this.propertyPath) 921 state.propertyPath = this.propertyPath; 922 923 if (this.propertyPath.length > 0 && !this.propertyPath[1]) 924 state.firstSelection = persistObject(this.getPathObject(1), this.context); 925 926 if (FBTrace.DBG_DOM) 927 FBTrace.sysout("dom.destroy; state:", state); 928 929 Firebug.Panel.destroy.apply(this, arguments); 930 }, 931 932 show: function(state) 933 { 934 if (!this.selection) 935 { 936 if (!state) 937 { 938 this.select(null); 939 return; 940 } 941 if (state.pathIndex > -1) 942 this.pathIndex = state.pathIndex; 943 if (state.viewPath) 944 this.viewPath = state.viewPath; 945 if (state.propertyPath) 946 this.propertyPath = state.propertyPath; 947 948 var selectObject = defaultObject = this.getDefaultSelection(this.context); 949 950 if (state.firstSelection) 951 { 952 var restored = state.firstSelection(this.context); 953 if (restored) 954 { 955 selectObject = restored; 956 this.objectPath = [defaultObject, restored]; 957 } 958 else 959 this.objectPath = [defaultObject]; 960 } 961 else 962 this.objectPath = [defaultObject]; 963 964 if (this.propertyPath.length > 1) 965 selectObject = this.resetPaths(selectObject); 966 else 967 this.propertyPath.push(null); // Sync with objectPath always containing a defalt object. 968 969 var selection = state.pathIndex < this.objectPath.length 970 ? this.getPathObject(state.pathIndex) 971 : this.getPathObject(this.objectPath.length-1); 972 973 if (FBTrace.DBG_DOM) 974 FBTrace.sysout("dom.show; selection:", selection); 975 976 this.select(selection); 977 } 978 }, 979 980 resetPaths: function(selectObject) 981 { 982 for (var i = 1; i < this.propertyPath.length; ++i) 983 { 984 var name = this.propertyPath[i]; 985 if (!name) 986 continue; 987 988 var object = selectObject; 989 try 990 { 991 selectObject = object[name]; 992 } 993 catch (exc) 994 { 995 selectObject = null; 996 } 997 998 if (selectObject) 999 { 1000 this.objectPath.push(new Property(object, name)); 1001 } 1002 else 1003 { 1004 // If we can't access a property, just stop 1005 this.viewPath.splice(i); 1006 this.propertyPath.splice(i); 1007 this.objectPath.splice(i); 1008 selectObject = this.getPathObject(this.objectPath.length-1); 1009 break; 1010 } 1011 } 1012 }, 1013 1014 hide: function() 1015 { 1016 var view = this.viewPath[this.pathIndex]; 1017 if (view && this.panelNode.scrollTop) 1018 view.scrollTop = this.panelNode.scrollTop; 1019 }, 1020 1021 getBreakOnNextTooltip: function(enabled) 1022 { 1023 return (enabled ? $STR("dom.Disable Break On Property Change") : 1024 $STR("dom.Break On Property Change")); 1025 }, 1026 1027 supportsObject: function(object) 1028 { 1029 if (object == null) 1030 return 1000; 1031 1032 if (typeof(object) == "undefined") 1033 return 1000; 1034 else if (object instanceof SourceLink) 1035 return 0; 1036 else 1037 return 1; // just agree to support everything but not aggressively. 1038 }, 1039 1040 refresh: function() 1041 { 1042 this.rebuild(true); 1043 }, 1044 1045 updateSelection: function(object) 1046 { 1047 if (FBTrace.DBG_DOM) 1048 FBTrace.sysout("dom.updateSelection; object=" + object, object); 1049 1050 var previousIndex = this.pathIndex; 1051 var previousView = previousIndex == -1 ? null : this.viewPath[previousIndex]; 1052 1053 var newPath = this.pathToAppend; 1054 delete this.pathToAppend; 1055 1056 var pathIndex = this.findPathIndex(object); 1057 if (newPath || pathIndex == -1) 1058 { 1059 this.toggles = {}; 1060 1061 if (newPath) 1062 { 1063 // Remove everything after the point where we are inserting, so we 1064 // essentially replace it with the new path 1065 if (previousView) 1066 { 1067 if (this.panelNode.scrollTop) 1068 previousView.scrollTop = this.panelNode.scrollTop; 1069 1070 this.objectPath.splice(previousIndex+1); 1071 this.propertyPath.splice(previousIndex+1); 1072 this.viewPath.splice(previousIndex+1); 1073 } 1074 1075 var value = this.getPathObject(previousIndex); 1076 if (!value) 1077 { 1078 if (FBTrace.DBG_ERRORS) 1079 FBTrace.sysout("dom.updateSelection no pathObject for "+previousIndex+"\n"); 1080 return; 1081 } 1082 1083 for (var i = 0; i < newPath.length; ++i) 1084 { 1085 var name = newPath[i]; 1086 var object = value; 1087 try 1088 { 1089 value = value[name]; 1090 } 1091 catch(exc) 1092 { 1093 if (FBTrace.DBG_ERRORS) 1094 FBTrace.sysout("dom.updateSelection FAILS at path_i="+i+" for name:"+name+"\n"); 1095 return; 1096 } 1097 1098 ++this.pathIndex; 1099 this.objectPath.push(new Property(object, name)); 1100 this.propertyPath.push(name); 1101 this.viewPath.push({toggles: this.toggles, scrollTop: 0}); 1102 } 1103 } 1104 else 1105 { 1106 this.toggles = {}; 1107 1108 var win = this.context.getGlobalScope(); 1109 if (object == win) 1110 { 1111 this.pathIndex = 0; 1112 this.objectPath = [win]; 1113 this.propertyPath = [null]; 1114 this.viewPath = [{toggles: this.toggles, scrollTop: 0}]; 1115 } 1116 else 1117 { 1118 this.pathIndex = 1; 1119 this.objectPath = [win, object]; 1120 this.propertyPath = [null, null]; 1121 this.viewPath = [ 1122 {toggles: {}, scrollTop: 0}, 1123 {toggles: this.toggles, scrollTop: 0} 1124 ]; 1125 } 1126 } 1127 1128 this.panelNode.scrollTop = 0; 1129 this.rebuild(); 1130 } 1131 else 1132 { 1133 this.pathIndex = pathIndex; 1134 1135 var view = this.viewPath[pathIndex]; 1136 this.toggles = view ? view.toggles : {}; 1137 1138 // Persist the current scroll location 1139 if (previousView && this.panelNode.scrollTop) 1140 previousView.scrollTop = this.panelNode.scrollTop; 1141 1142 this.rebuild(false, view ? view.scrollTop : 0); 1143 } 1144 1145 }, 1146 1147 getObjectPath: function(object) 1148 { 1149 return this.objectPath; 1150 }, 1151 1152 getDefaultSelection: function() 1153 { 1154 return this.context.getGlobalScope(); 1155 }, 1156 1157 updateOption: function(name, value) 1158 { 1159 const optionMap = {showUserProps: 1, showUserFuncs: 1, showDOMProps: 1, 1160 showDOMFuncs: 1, showDOMConstants: 1}; 1161 if ( optionMap.hasOwnProperty(name) ) 1162 this.rebuild(true); 1163 }, 1164 1165 getOptionsMenuItems: function() 1166 { 1167 return [ 1168 optionMenu("ShowUserProps", "showUserProps"), 1169 optionMenu("ShowUserFuncs", "showUserFuncs"), 1170 optionMenu("ShowDOMProps", "showDOMProps"), 1171 optionMenu("ShowDOMFuncs", "showDOMFuncs"), 1172 optionMenu("ShowDOMConstants", "showDOMConstants"), 1173 "-", 1174 {label: "Refresh", command: bindFixed(this.rebuild, this, true) } 1175 ]; 1176 }, 1177 1178 getContextMenuItems: function(object, target) 1179 { 1180 if (FBTrace.DBG_DOM) 1181 FBTrace.sysout("dom.getContextMenuItems;", object); 1182 1183 var row = getAncestorByClass(target, "memberRow"); 1184 1185 var items = []; 1186 1187 if (row) 1188 { 1189 var rowName = getRowName(row); 1190 var rowObject = this.getRowObject(row); 1191 var rowValue = this.getRowPropertyValue(row); 1192 1193 var isWatch = hasClass(row, "watchRow"); 1194 var isStackFrame = rowObject instanceof jsdIStackFrame; 1195 1196 items.push( 1197 "-", 1198 {label: "Copy Name", 1199 command: bindFixed(this.copyName, this, row) }, 1200 {label: "Copy Path", 1201 command: bindFixed(this.copyPath, this, row) } 1202 ); 1203 1204 if (typeof(rowValue) == "string" || typeof(rowValue) == "number") 1205 { 1206 // Functions already have a copy item in their context menu 1207 items.push( 1208 {label: "CopyValue", 1209 command: bindFixed(this.copyProperty, this, row) } 1210 ); 1211 } 1212 1213 items.push( 1214 "-", 1215 {label: isWatch ? "EditWatch" : (isStackFrame ? "EditVariable" : "EditProperty"), 1216 command: bindFixed(this.editProperty, this, row) } 1217 ); 1218 1219 if (isWatch || (!isStackFrame && !isDOMMember(rowObject, rowName))) 1220 { 1221 items.push( 1222 {label: isWatch ? "DeleteWatch" : "DeleteProperty", 1223 command: bindFixed(this.deleteProperty, this, row) } 1224 ); 1225 } 1226 1227 var member = row ? row.domObject : null; 1228 if (!isDOMMember(rowObject, rowName) && member && member.breakable) 1229 { 1230 items.push( 1231 "-", 1232 {label: "html.dom.label.Break On Property Change", type: "checkbox", 1233 checked: this.context.dom.breakpoints.findBreakpoint(rowObject, rowName), 1234 command: bindFixed(this.breakOnProperty, this, row)} 1235 ); 1236 } 1237 } 1238 1239 items.push( 1240 "-", 1241 {label: "Refresh", command: bindFixed(this.rebuild, this, true) } 1242 ); 1243 1244 return items; 1245 }, 1246 1247 getEditor: function(target, value) 1248 { 1249 if (!this.editor) 1250 this.editor = new DOMEditor(this.document); 1251 1252 return this.editor; 1253 } 1254 }); 1255 1256 // ************************************************************************************************ 1257 1258 var DOMMainPanel = Firebug.DOMPanel = function () {}; 1259 1260 Firebug.DOMPanel.DirTable = DirTablePlate; 1261 1262 DOMMainPanel.prototype = extend(Firebug.DOMBasePanel.prototype, 1263 { 1264 selectRow: function(row, target) 1265 { 1266 if (!target) 1267 target = row.lastChild.firstChild; 1268 1269 if (!target || !target.repObject) 1270 return; 1271 1272 this.pathToAppend = getPath(row); 1273 1274 // If the object is inside an array, look up its index 1275 var valueBox = row.lastChild.firstChild; 1276 if (hasClass(valueBox, "objectBox-array")) 1277 { 1278 var arrayIndex = FirebugReps.Arr.getItemIndex(target); 1279 this.pathToAppend.push(arrayIndex); 1280 } 1281 1282 // Make sure we get a fresh status path for the object, since otherwise 1283 // it might find the object in the existing path and not refresh it 1284 Firebug.chrome.clearStatusPath(); 1285 1286 this.select(target.repObject, true); 1287 }, 1288 1289 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1290 1291 onClick: function(event) 1292 { 1293 var repNode = Firebug.getRepNode(event.target); 1294 if (repNode) 1295 { 1296 var row = getAncestorByClass(event.target, "memberRow"); 1297 if (row) 1298 { 1299 this.selectRow(row, repNode); 1300 cancelEvent(event); 1301 } 1302 } 1303 }, 1304 1305 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1306 // extends Panel 1307 1308 name: "dom", 1309 searchable: true, 1310 statusSeparator: ">", 1311 1312 initialize: function() 1313 { 1314 this.onClick = bind(this.onClick, this); 1315 1316 Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments); 1317 }, 1318 1319 initializeNode: function(oldPanelNode) 1320 { 1321 this.panelNode.addEventListener("click", this.onClick, false); 1322 dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']); 1323 }, 1324 1325 destroyNode: function() 1326 { 1327 this.panelNode.removeEventListener("click", this.onClick, false); 1328 dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']); 1329 }, 1330 1331 search: function(text, reverse) 1332 { 1333 if (!text) 1334 { 1335 delete this.currentSearch; 1336 this.highlightRow(null); 1337 return false; 1338 } 1339 1340 var row; 1341 if (this.currentSearch && text == this.currentSearch.text) 1342 row = this.currentSearch.findNext(true, undefined, reverse, Firebug.Search.isCaseSensitive(text)); 1343 else 1344 { 1345 function findRow(node) { return getAncestorByClass(node, "memberRow"); } 1346 this.currentSearch = new TextSearch(this.panelNode, findRow); 1347 row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text)); 1348 } 1349 1350 if (row) 1351 { 1352 var sel = this.document.defaultView.getSelection(); 1353 sel.removeAllRanges(); 1354 sel.addRange(this.currentSearch.range); 1355 1356 scrollIntoCenterView(row, this.panelNode); 1357 1358 this.highlightRow(row); 1359 dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, row]); 1360 return true; 1361 } 1362 else 1363 { 1364 dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, null]); 1365 return false; 1366 } 1367 } 1368 }); 1369 1370 // ************************************************************************************************ 1371 1372 function DOMSidePanel() {} 1373 1374 DOMSidePanel.prototype = extend(Firebug.DOMBasePanel.prototype, 1375 { 1376 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1377 // extends Panel 1378 1379 name: "domSide", 1380 parentPanel: "html", 1381 order: 3, 1382 1383 initializeNode: function(oldPanelNode) 1384 { 1385 dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']); 1386 }, 1387 1388 destroyNode: function() 1389 { 1390 dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']); 1391 }, 1392 }); 1393 1394 // ************************************************************************************************ 1395 1396 function WatchPanel() {} 1397 1398 WatchPanel.prototype = extend(Firebug.DOMBasePanel.prototype, 1399 { 1400 tag: DirTablePlate.watchTag, 1401 1402 rebuild: function() 1403 { 1404 this.updateSelection(this.selection); 1405 }, 1406 1407 showEmptyMembers: function() 1408 { 1409 this.tag.replace({domPanel: this, toggles: {}}, this.panelNode); 1410 }, 1411 1412 addWatch: function(expression) 1413 { 1414 if (!this.watches) 1415 this.watches = []; 1416 1417 this.watches.splice(0, 0, expression); 1418 this.rebuild(true); 1419 }, 1420 1421 removeWatch: function(expression) 1422 { 1423 if (!this.watches) 1424 return; 1425 1426 var index = this.watches.indexOf(expression); 1427 if (index != -1) 1428 this.watches.splice(index, 1); 1429 }, 1430 1431 editNewWatch: function(value) 1432 { 1433 var watchNewRow = this.panelNode.getElementsByClassName("watchNewRow").item(0); 1434 if (watchNewRow) 1435 this.editProperty(watchNewRow, value); 1436 }, 1437 1438 setWatchValue: function(row, value) 1439 { 1440 var rowIndex = getWatchRowIndex(row); 1441 this.watches[rowIndex] = value; 1442 this.rebuild(true); 1443 }, 1444 1445 deleteWatch: function(row) 1446 { 1447 var rowIndex = getWatchRowIndex(row); 1448 this.watches.splice(rowIndex, 1); 1449 this.rebuild(true); 1450 1451 this.context.setTimeout(bindFixed(function() 1452 { 1453 this.showToolbox(null); 1454 }, this)); 1455 }, 1456 1457 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1458 1459 showToolbox: function(row) 1460 { 1461 var toolbox = this.getToolbox(); 1462 if (row) 1463 { 1464 if (hasClass(row, "editing")) 1465 return; 1466 1467 toolbox.watchRow = row; 1468 1469 var offset = getClientOffset(row); 1470 toolbox.style.top = offset.y + "px"; 1471 this.panelNode.appendChild(toolbox); 1472 } 1473 else 1474 { 1475 delete toolbox.watchRow; 1476 if (toolbox.parentNode) 1477 toolbox.parentNode.removeChild(toolbox); 1478 } 1479 }, 1480 1481 getToolbox: function() 1482 { 1483 if (!this.toolbox) 1484 this.toolbox = ToolboxPlate.tag.replace({domPanel: this}, this.document); 1485 1486 return this.toolbox; 1487 }, 1488 1489 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1490 1491 onMouseDown: function(event) 1492 { 1493 var watchNewRow = getAncestorByClass(event.target, "watchNewRow"); 1494 if (watchNewRow) 1495 { 1496 this.editProperty(watchNewRow); 1497 cancelEvent(event); 1498 } 1499 }, 1500 1501 onMouseOver: function(event) 1502 { 1503 var watchRow = getAncestorByClass(event.target, "watchRow"); 1504 if (watchRow) 1505 this.showToolbox(watchRow); 1506 }, 1507 1508 onMouseOut: function(event) 1509 { 1510 if (isAncestor(event.relatedTarget, this.getToolbox())) 1511 return; 1512 1513 var watchRow = getAncestorByClass(event.relatedTarget, "watchRow"); 1514 if (!watchRow) 1515 this.showToolbox(null); 1516 }, 1517 1518 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1519 // extends Panel 1520 1521 name: "watches", 1522 order: 0, 1523 parentPanel: "script", 1524 1525 initialize: function() 1526 { 1527 this.onMouseDown = bind(this.onMouseDown, this); 1528 this.onMouseOver = bind(this.onMouseOver, this); 1529 this.onMouseOut = bind(this.onMouseOut, this); 1530 1531 Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments); 1532 }, 1533 1534 destroy: function(state) 1535 { 1536 state.watches = this.watches; 1537 1538 Firebug.Panel.destroy.apply(this, arguments); 1539 }, 1540 1541 show: function(state) 1542 { 1543 if (state && state.watches) 1544 this.watches = state.watches; 1545 }, 1546 1547 initializeNode: function(oldPanelNode) 1548 { 1549 this.panelNode.addEventListener("mousedown", this.onMouseDown, false); 1550 this.panelNode.addEventListener("mouseover", this.onMouseOver, false); 1551 this.panelNode.addEventListener("mouseout", this.onMouseOut, false); 1552 dispatch([Firebug.A11yModel], "onInitializeNode", [this, 'console']); 1553 }, 1554 1555 destroyNode: function() 1556 { 1557 this.panelNode.removeEventListener("mousedown", this.onMouseDown, false); 1558 this.panelNode.removeEventListener("mouseover", this.onMouseOver, false); 1559 this.panelNode.removeEventListener("mouseout", this.onMouseOut, false); 1560 dispatch([Firebug.A11yModel], "onDestroyNode", [this, 'console']); 1561 }, 1562 1563 refresh: function() 1564 { 1565 this.rebuild(true); 1566 1567 }, 1568 1569 updateSelection: function(object) 1570 { 1571 dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]); 1572 var frame = this.context.currentFrame; 1573 1574 var newFrame = frame && frame.isValid && frame.script != this.lastScript; 1575 if (newFrame) 1576 { 1577 this.toggles = {}; 1578 this.lastScript = frame.script; 1579 } 1580 1581 var members = []; 1582 1583 if (this.watches) 1584 { 1585 for (var i = 0; i < this.watches.length; ++i) 1586 { 1587 var expr = this.watches[i]; 1588 var value = null; 1589 Firebug.CommandLine.evaluate(expr, this.context, null, this.context.getGlobalScope(), 1590 function success(result, context) 1591 { 1592 value = result; 1593 }, 1594 function failed(result, context) 1595 { 1596 var exc = result; 1597 value = new ErrorCopy(exc+""); 1598 } 1599 ); 1600 1601 addMember(object, "watch", members, expr, value, 0); 1602 } 1603 } 1604 1605 if (frame && frame.isValid) 1606 { 1607 var thisVar = unwrapIValue(frame.thisValue); 1608 addMember(object, "user", members, "this", thisVar, 0); 1609 1610 var scopeChain = this.generateScopeChain(frame.scope); 1611 addMember(object, "scopes", members, "scopeChain", scopeChain, 0); 1612 1613 members.push.apply(members, this.getMembers(scopeChain[0], 0, this.context)); 1614 } 1615 1616 this.expandMembers(members, this.toggles, 0, 0, this.context); 1617 this.showMembers(members, !newFrame); 1618 }, 1619 1620 generateScopeChain: function (scope) 1621 { 1622 var ret = []; 1623 while (scope) { 1624 var scopeVars; 1625 // getWrappedValue will not contain any variables for closure 1626 // scopes, so we want to special case this to get all variables 1627 // in all cases. 1628 if (scope.jsClassName == "Call") { 1629 scopeVars = {}; 1630 var listValue = {value: null}, lengthValue = {value: 0}; 1631 scope.getProperties(listValue, lengthValue); 1632 1633 for (var i = 0; i < lengthValue.value; ++i) 1634 { 1635 var prop = listValue.value[i]; 1636 var name = unwrapIValue(prop.name); 1637 if (ignoreVars[name] == 1) 1638 { 1639 if (FBTrace.DBG_DOM) 1640 FBTrace.sysout("dom.generateScopeChain: ignoreVars: " + name); 1641 continue; 1642 } 1643 1644 scopeVars[name] = unwrapIValue(prop.value); 1645 } 1646 } else { 1647 scopeVars = unwrapIValue(scope); 1648 } 1649 1650 if (scopeVars && scopeVars.hasOwnProperty) 1651 { 1652 if (!scopeVars.hasOwnProperty("toString")) { 1653 (function() { 1654 var className = scope.jsClassName; 1655 scopeVars.toString = function() { 1656 return $STR(className + " Scope"); 1657 }; 1658 })(); 1659 } 1660 1661 ret.push(scopeVars); 1662 } 1663 else 1664 { 1665 if (FBTrace.DBG_ERRORS) 1666 FBTrace.sysout("dom .generateScopeChain: bad scopeVars"); 1667 } 1668 scope = scope.jsParent; 1669 } 1670 1671 ret.toString = function() { 1672 return $STR("Scope Chain"); 1673 }; 1674 1675 return ret; 1676 }, 1677 1678 }); 1679 1680 // ************************************************************************************************ 1681 // Local Helpers 1682 1683 function DOMEditor(doc) 1684 { 1685 this.box = this.tag.replace({}, doc, this); 1686 this.input = this.box; 1687 1688 this.tabNavigation = false; 1689 this.tabCompletion = true; 1690 this.completeAsYouType = false; 1691 this.fixedWidth = true; 1692 1693 this.autoCompleter = Firebug.CommandLine.autoCompleter; 1694 } 1695 1696 DOMEditor.prototype = domplate(Firebug.InlineEditor.prototype, 1697 { 1698 tag: 1699 INPUT({"class": "fixedWidthEditor a11yFocusNoTab", 1700 type: "text", title:$STR("NewWatch"), 1701 oninput: "$onInput", onkeypress: "$onKeyPress"}), 1702 1703 endEditing: function(target, value, cancel) 1704 { 1705 // XXXjoe Kind of hackish - fix me 1706 delete this.panel.context.thisValue; 1707 1708 if (cancel || value == "") 1709 return; 1710 1711 var row = getAncestorByClass(target, "memberRow"); 1712 dispatch([Firebug.A11yModel], 'onWatchEndEditing', [this.panel]); 1713 if (!row) 1714 this.panel.addWatch(value); 1715 else if (hasClass(row, "watchRow")) 1716 this.panel.setWatchValue(row, value); 1717 else 1718 this.panel.setPropertyValue(row, value); 1719 } 1720 }); 1721 1722 // ************************************************************************************************ 1723 // Local Helpers 1724 1725 function isClassFunction(fn) 1726 { 1727 try 1728 { 1729 for (var name in fn.prototype) 1730 return true; 1731 } catch (exc) {} 1732 return false; 1733 } 1734 1735 function isArguments(obj) 1736 { 1737 try 1738 { 1739 return isFinite(obj.length) && obj.length > 0 && typeof obj.callee === "function"; 1740 } catch (exc) {} 1741 return false; 1742 } 1743 1744 function addMember(object, type, props, name, value, level, order, context) 1745 { 1746 var rep = Firebug.getRep(value); // do this first in case a call to instanceof reveals contents 1747 var tag = rep.shortTag ? rep.shortTag : rep.tag; 1748 1749 var valueType = typeof(value); 1750 var hasChildren = hasProperties(value) && !(value instanceof ErrorCopy) && 1751 (valueType == "function" || (valueType == "object" && value != null) 1752 || (valueType == "string" && value.length > Firebug.stringCropLength)); 1753 1754 // Special case for "arguments", which is not enumerable by for...in statement 1755 // and so, hasProperties always returns false. 1756 if (!hasChildren && value) // arguments will never be falsy if the arguments exist 1757 hasChildren = isArguments(value); 1758 1759 var member = { 1760 object: object, 1761 name: name, 1762 value: value, 1763 type: type, 1764 rowClass: "memberRow-"+type, 1765 open: "", 1766 order: order, 1767 level: level, 1768 indent: level*16, 1769 hasChildren: hasChildren, 1770 tag: tag 1771 }; 1772 1773 // The context doesn't have to be specified (e.g. in case of Watch panel that is based 1774 // on the same template as the DOM panel, but doesn't show any breakpoints). 1775 if (context) 1776 { 1777 // xxxHonza: Support for object change not implemented yet. 1778 member.breakable = !hasChildren; 1779 1780 // xxxHonza: Disable breaking on direct window properties, see #520572 1781 if (object instanceof Ci.nsIDOMWindow) 1782 member.breakable = false; 1783 1784 var breakpoints = context.dom.breakpoints; 1785 var bp = breakpoints.findBreakpoint(object, name); 1786 if (bp) 1787 { 1788 member.breakpoint = true; 1789 member.disabledBreakpoint = !bp.checked; 1790 } 1791 } 1792 1793 // If the property is implemented using a getter function (and there is no setter 1794 // implemented) use a "get" prefix that is displayed in the UI. 1795 var o = unwrapObject(object); 1796 member.prefix = (o.__lookupGetter__(name) && !o.__lookupSetter__(name)) ? "get " : ""; 1797 1798 props.push(member); 1799 return member; 1800 } 1801 1802 function getWatchRowIndex(row) 1803 { 1804 var index = -1; 1805 for (; row && hasClass(row, "watchRow"); row = row.previousSibling) 1806 ++index; 1807 return index; 1808 } 1809 1810 function getRowName(row) 1811 { 1812 var labelNode = row.getElementsByClassName("memberLabelCell").item(0); 1813 return labelNode.textContent; 1814 } 1815 1816 function getRowValue(row) 1817 { 1818 var valueNode = row.getElementsByClassName("memberValueCell").item(0); 1819 return valueNode.firstChild.repObject; 1820 } 1821 1822 function getRowOwnerObject(row) 1823 { 1824 var parentRow = getParentRow(row); 1825 if (parentRow) 1826 return getRowValue(parentRow); 1827 } 1828 1829 function getParentRow(row) 1830 { 1831 var level = parseInt(row.getAttribute("level"))-1; 1832 for (row = row.previousSibling; row; row = row.previousSibling) 1833 { 1834 if (parseInt(row.getAttribute("level")) == level) 1835 return row; 1836 } 1837 } 1838 1839 function getPath(row) 1840 { 1841 var name = getRowName(row); 1842 var path = [name]; 1843 1844 var level = parseInt(row.getAttribute("level"))-1; 1845 for (row = row.previousSibling; row; row = row.previousSibling) 1846 { 1847 if (parseInt(row.getAttribute("level")) == level) 1848 { 1849 var name = getRowName(row); 1850 path.splice(0, 0, name); 1851 1852 --level; 1853 } 1854 } 1855 1856 return path; 1857 } 1858 1859 function findRow(parentNode, object) 1860 { 1861 var rows = parentNode.getElementsByClassName("memberRow"); 1862 for (var i=0; i<rows.length; i++) 1863 { 1864 var row = rows[i]; 1865 if (object == row.domObject.object) 1866 return row; 1867 } 1868 1869 return row; 1870 } 1871 1872 // ************************************************************************************************ 1873 1874 Firebug.DOMModule.DebuggerListener = 1875 { 1876 getBreakpoints: function(context, groups) 1877 { 1878 if (!context.dom.breakpoints.isEmpty()) 1879 groups.push(context.dom.breakpoints); 1880 } 1881 }; 1882 1883 Firebug.DOMModule.BreakpointRep = domplate(Firebug.Rep, 1884 { 1885 inspectable: false, 1886 1887 tag: 1888 DIV({"class": "breakpointRow focusRow", _repObject: "$bp", 1889 role: "option", "aria-checked": "$bp.checked"}, 1890 DIV({"class": "breakpointBlockHead", onclick: "$onEnable"}, 1891 INPUT({"class": "breakpointCheckbox", type: "checkbox", 1892 _checked: "$bp.checked", tabindex : "-1"}), 1893 SPAN({"class": "breakpointName"}, "$bp.propName"), 1894 IMG({"class": "closeButton", src: "blank.gif", onclick: "$onRemove"}) 1895 ), 1896 DIV({"class": "breakpointCode"}, 1897 TAG("$bp.object|getObjectTag", {object: "$bp.object"}) 1898 ) 1899 ), 1900 1901 getObjectTag: function(object) 1902 { 1903 var rep = Firebug.getRep(object); 1904 return rep.shortTag ? rep.shortTag : rep.tag; 1905 }, 1906 1907 onRemove: function(event) 1908 { 1909 cancelEvent(event); 1910 1911 if (!hasClass(event.target, "closeButton")) 1912 return; 1913 1914 var bpPanel = Firebug.getElementPanel(event.target); 1915 var context = bpPanel.context; 1916 1917 // Remove from list of breakpoints. 1918 var row = getAncestorByClass(event.target, "breakpointRow"); 1919 var bp = row.repObject; 1920 context.dom.breakpoints.removeBreakpoint(bp.object, bp.propName); 1921 1922 // Remove from the UI. 1923 bpPanel.noRefresh = true; 1924 bpPanel.removeRow(row); 1925 bpPanel.noRefresh = false; 1926 1927 var domPanel = context.getPanel("dom", true); 1928 if (domPanel) 1929 { 1930 var domRow = findRow(domPanel.panelNode, bp.object); 1931 if (domRow) 1932 { 1933 domRow.removeAttribute("breakpoint"); 1934 domRow.removeAttribute("disabledBreakpoint"); 1935 } 1936 } 1937 }, 1938 1939 onEnable: function(event) 1940 { 1941 var checkBox = event.target; 1942 if (!hasClass(checkBox, "breakpointCheckbox")) 1943 return; 1944 1945 var bpPanel = Firebug.getElementPanel(event.target); 1946 var context = bpPanel.context; 1947 1948 var bp = getAncestorByClass(checkBox, "breakpointRow").repObject; 1949 bp.checked = checkBox.checked; 1950 1951 var domPanel = context.getPanel("dom", true); 1952 if (domPanel) 1953 { 1954 var row = findRow(domPanel.panelNode, bp.object); 1955 if (row) 1956 row.setAttribute("disabledBreakpoint", bp.checked ? "false" : "true"); 1957 } 1958 }, 1959 1960 supportsObject: function(object) 1961 { 1962 return object instanceof Breakpoint; 1963 } 1964 }); 1965 1966 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 1967 1968 function Breakpoint(object, propName, objectPath, context) 1969 { 1970 this.context = context; 1971 this.propName = propName; 1972 this.objectPath = objectPath; 1973 this.object = object; 1974 this.checked = true; 1975 } 1976 1977 Breakpoint.prototype = 1978 { 1979 watchProperty: function() 1980 { 1981 if (FBTrace.DBG_DOM) 1982 FBTrace.sysout("dom.watch; property: " + this.propName); 1983 1984 if (!this.object) 1985 return; 1986 1987 try 1988 { 1989 var self = this; 1990 this.object.watch(this.propName, function handler(prop, oldval, newval) 1991 { 1992 // XXXjjb Beware: in playing with this feature I hit too much recursion multiple times with console.log 1993 // TODO Do something cute in the UI with the error bubble thing 1994 if (self.checked) 1995 { 1996 self.context.breakingCause = { 1997 title: $STR("dom.Break On Property"), 1998 message: cropString(prop, 200), 1999 prevValue: oldval, 2000 newValue: newval 2001 }; 2002 2003 Firebug.Breakpoint.breakNow(self.context.getPanel("dom", true)); 2004 } 2005 return newval; 2006 }); 2007 } 2008 catch (exc) 2009 { 2010 if (FBTrace.DBG_ERRORS) 2011 FBTrace.sysout("dom.watch; object FAILS " + exc, exc); 2012 return false; 2013 } 2014 2015 return true; 2016 }, 2017 2018 unwatchProperty: function() 2019 { 2020 if (FBTrace.DBG_DOM) 2021 FBTrace.sysout("dom.unwatch; property: " + this.propName, this.object); 2022 2023 if (!this.object) 2024 return; 2025 2026 try 2027 { 2028 this.object.unwatch(this.propName); 2029 } 2030 catch (exc) 2031 { 2032 if (FBTrace.DBG_ERRORS) 2033 FBTrace.sysout("dom.unwatch; object FAILS " + exc, exc); 2034 } 2035 } 2036 } 2037 2038 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2039 2040 function DOMBreakpointGroup() 2041 { 2042 this.breakpoints = []; 2043 } 2044 2045 DOMBreakpointGroup.prototype = extend(new Firebug.Breakpoint.BreakpointGroup(), 2046 { 2047 name: "domBreakpoints", 2048 title: $STR("dom.label.DOM Breakpoints"), 2049 2050 addBreakpoint: function(object, propName, panel, row) 2051 { 2052 var path = panel.getPropertyPath(row); 2053 path.pop(); 2054 2055 // We don't want the last dot. 2056 if (path.length > 0 && path[path.length-1] == ".") 2057 path.pop(); 2058 2059 var objectPath = path.join(""); 2060 if (FBTrace.DBG_DOM) 2061 FBTrace.sysout("dom.addBreakpoint; " + objectPath, path); 2062 2063 var bp = new Breakpoint(object, propName, objectPath, panel.context); 2064 if (bp.watchProperty()); 2065 this.breakpoints.push(bp); 2066 }, 2067 2068 removeBreakpoint: function(object, propName) 2069 { 2070 var bp = this.findBreakpoint(object, propName); 2071 if (bp) 2072 { 2073 bp.unwatchProperty(); 2074 remove(this.breakpoints, bp); 2075 } 2076 }, 2077 2078 matchBreakpoint: function(bp, args) 2079 { 2080 var object = args[0]; 2081 var propName = args[1]; 2082 return bp.object == object && bp.propName == propName; 2083 }, 2084 2085 // Persistence 2086 load: function(context) 2087 { 2088 var panelState = getPersistedState(context, "dom"); 2089 if (panelState.breakpoints) 2090 this.breakpoints = panelState.breakpoints; 2091 2092 this.enumerateBreakpoints(function(bp) 2093 { 2094 try 2095 { 2096 // xxxHonza: Firebug.CommandLine.evaluate should be reused if possible. 2097 // xxxJJB: The Components.utils.evalInSandbox fails from some reason. 2098 var expr = "context.window.wrappedJSObject." + bp.objectPath; 2099 bp.object = eval(expr); 2100 bp.watchProperty(); 2101 2102 if (FBTrace.DBG_DOM) 2103 FBTrace.sysout("dom.DOMBreakpointGroup.load; " + bp.objectPath, bp); 2104 } 2105 catch (err) 2106 { 2107 if (FBTrace.DBG_ERROR || FBTrace.DBG_DOM) 2108 FBTrace.sysout("dom.DOMBreakpointGroup.load; ERROR " + bp.objectPath, err); 2109 } 2110 }); 2111 }, 2112 2113 store: function(context) 2114 { 2115 this.enumerateBreakpoints(function(bp) 2116 { 2117 bp.object = null; 2118 }); 2119 2120 var panelState = getPersistedState(context, "dom"); 2121 panelState.breakpoints = this.breakpoints; 2122 }, 2123 }); 2124 2125 // ************************************************************************************************ 2126 2127 Firebug.registerModule(Firebug.DOMModule); 2128 Firebug.registerPanel(DOMMainPanel); 2129 Firebug.registerPanel(DOMSidePanel); 2130 Firebug.registerPanel(WatchPanel); 2131 Firebug.registerRep(Firebug.DOMModule.BreakpointRep); 2132 2133 // ************************************************************************************************ 2134 2135 }}); 2136 2137