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